Проект "Рынок заведений общественного питания Москвы"¶
Описание проекта:
Инвесторы из фонда «Shut Up and Take My Money» решили попробовать себя в новой области и открыть заведение общественного питания в Москве - кофейню. Будем считать, что заказчики не боятся конкуренции в этой сфере, ведь кофеен в больших городах уже достаточно.
Они просят аналитика подготовить исследование рынка Москвы, найти интересные особенности и презентовать полученные результаты, которые в будущем помогут в выборе подходящего инвесторам места.
Основателям фонда «Shut Up and Take My Money» не даёт покоя успех сериала «Друзья». Их мечта — открыть такую же крутую и доступную, как «Central Perk», кофейню в Москве.
Ссылка на презентацию: PDF
Вам доступен датасет с заведениями общественного питания Москвы, составленный на основе данных сервисов Яндекс Карты и Яндекс Бизнес на лето 2022 года. Информация, размещённая в сервисе Яндекс Бизнес, могла быть добавлена пользователями или найдена в общедоступных источниках. Она носит исключительно справочный характер.
Цель проекта: дать заказчикам рекомендацию для открытия нового заведения общественного питания в г.Москва.
Задачи проекта:
- провести исследование рынка общественного питания Москвы
- подготовить рекомендации заказчику для открытия нового заведения общественного питания в Москве.
Используемые инструменты
python
pandas
numpy
matplotlib
seaborn
plotly
json
folium
План работы
Загрузить и данные и выполнить их предобработку.
- Загрузить данные. При необходимости изменить или дополнить представление данных, типы данных.
- Обработать пропуски и дубликаты.
- Изучить значения столбцов на предмет ошибочных данных и аномалий.
- Добавить столбцы:
- столбец street с названиями улиц из столбца с адресом;
- столбец is_24_7 с обозначением, что заведение работает ежедневно и круглосуточно (24/7):
- True — если заведение работает ежедневно и круглосуточно;
- False — в противоположном случае.
Провести исследовательский анализ данных.
- Исследовать количество объектов общественного питания по категориям.
- Исследовать количество посадочных мест в местах по категориям.
- Рассмотреть и изобразить соотношение сетевых и несетевых заведений в датасете.
- Определить, какие категории заведений чаще являются сетевыми.
- Сгруппировать данные по названиям заведений и найти топ-15 популярных сетей в Москве.
- Выяснить, какие административные районы Москвы присутствуют в датасете. Отобразить общее количество заведений и количество заведений каждой категории по районам.
- Визуализировать распределение средних рейтингов по категориям заведений.
- Построить фоновую картограмму (хороплет) со средним рейтингом заведений каждого района.
- Отобразить все заведения датасета на карте с помощью кластеров средствами библиотеки folium.
- Найти топ-15 улиц по количеству заведений. Построить график распределения количества заведений и их категорий по этим улицам.
- Найти улицы, на которых находится только один объект общепита и описать эти заведения.
- Посчитать медиану значения средних чеков для каждого района. Построить фоновую картограмму (хороплет) с полученными значениями для каждого района. Проанализировать цены в центральном административном округе и других и влияние удалённости от центра на цены в заведениях.
Исследовать данные и дать рекомендацию для открытия новой кофейни.
- Изучить количество кофеен, их распределение по районам, особенности их расположения.
- Изучить режим работы и количество круглосуточных кофеен.
- Изучить рейтинги кофеен и их распределение по районам.
- Определить, на какую стоимость чашки капучино стоит ориентироваться при открытии кофейни.
- Визуализировать данные и дать рекомендации.
Подготовить презентацию.
Сделать общий вывод.
Описание данных
Файл moscow_places.csv:
- name — название заведения;
- address — адрес заведения;
- category — категория заведения, например «кафе», «пиццерия» или «кофейня»;
- hours — информация о днях и часах работы;
- lat — широта географической точки, в которой находится заведение;
- lng — долгота географической точки, в которой находится заведение;
- rating — рейтинг заведения по оценкам пользователей в Яндекс Картах (высшая оценка — 5.0);
- price — категория цен в заведении, например «средние», «ниже среднего», «выше среднего» и так далее;
- avg_bill — строка, которая хранит среднюю стоимость заказа в виде диапазона, например:
- «Средний счёт: 1000–1500 ₽»;
- «Цена чашки капучино: 130–220 ₽»;
- «Цена бокала пива: 400–600 ₽».
- middle_avg_bill — число с оценкой среднего чека, которое указано только для значений из столбца avg_bill, начинающихся с подстроки «Средний счёт»:
- Если в строке указан ценовой диапазон из двух значений, в столбец войдёт медиана этих двух значений.
- Если в строке указано одно число — цена без диапазона, то в столбец войдёт это число.
- Если значения нет или оно не начинается с подстроки «Средний счёт», то в столбец ничего не войдёт.
- middle_coffee_cup — число с оценкой одной чашки капучино, которое указано только для значений из столбца avg_bill, начинающихся с подстроки «Цена одной чашки капучино»:
- Если в строке указан ценовой диапазон из двух значений, в столбец войдёт медиана этих двух значений.
- Если в строке указано одно число — цена без диапазона, то в столбец войдёт это число.
- Если значения нет или оно не начинается с подстроки «Цена одной чашки капучино», то в столбец ничего не войдёт.
- chain — число, выраженное 0 или 1, которое показывает, является ли заведение сетевым (для маленьких сетей могут встречаться ошибки):
- 0 — заведение не является сетевым
- 1 — заведение является сетевым
- district — административный район, в котором находится заведение, например Центральный административный округ;
- seats — количество посадочных мест.
Границы районов Москвы, которые встречаются в датасете, хранятся в файле admin_level_geomap.geojson
Загрузка и предобработка данных¶
Загрузка данных¶
#импорт необходимых библиотек и установка инструментов
!pip install missingno
import missingno as msno
!pip install folium
import pandas as pd
import matplotlib.pyplot as plt
import plotly.express as px
import plotly.graph_objs as go
import seaborn as sns
import json
import numpy as np
import folium
from folium import Map, Marker, Choropleth
from folium.plugins import MarkerCluster
#настройка ширины отображаемых колонок
pd.set_option('max_colwidth', 120)
#путь для сохранения изображений для презентации
pics_path = 'pics_moscow_catering_market_research\\'
#выбор цветового стиля и палитры по умолчанию
plt.style.use('ggplot')
sns.set_palette('tab20')
#отключение текстовых предупреждений
import warnings
warnings.filterwarnings("ignore")
Requirement already satisfied: missingno in c:\programdata\anaconda3\lib\site-packages (0.5.2) Requirement already satisfied: numpy in c:\programdata\anaconda3\lib\site-packages (from missingno) (1.26.4) Requirement already satisfied: matplotlib in c:\programdata\anaconda3\lib\site-packages (from missingno) (3.8.3) Requirement already satisfied: scipy in c:\programdata\anaconda3\lib\site-packages (from missingno) (1.13.0) Requirement already satisfied: seaborn in c:\programdata\anaconda3\lib\site-packages (from missingno) (0.13.2) Requirement already satisfied: contourpy>=1.0.1 in c:\programdata\anaconda3\lib\site-packages (from matplotlib->missingno) (1.2.0) Requirement already satisfied: cycler>=0.10 in c:\programdata\anaconda3\lib\site-packages (from matplotlib->missingno) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in c:\programdata\anaconda3\lib\site-packages (from matplotlib->missingno) (4.50.0) Requirement already satisfied: kiwisolver>=1.3.1 in c:\programdata\anaconda3\lib\site-packages (from matplotlib->missingno) (1.4.5) Requirement already satisfied: packaging>=20.0 in c:\programdata\anaconda3\lib\site-packages (from matplotlib->missingno) (23.1) Requirement already satisfied: pillow>=8 in c:\programdata\anaconda3\lib\site-packages (from matplotlib->missingno) (10.2.0) Requirement already satisfied: pyparsing>=2.3.1 in c:\programdata\anaconda3\lib\site-packages (from matplotlib->missingno) (3.1.2) Requirement already satisfied: python-dateutil>=2.7 in c:\programdata\anaconda3\lib\site-packages (from matplotlib->missingno) (2.8.2) Requirement already satisfied: pandas>=1.2 in c:\programdata\anaconda3\lib\site-packages (from seaborn->missingno) (2.1.4) Requirement already satisfied: pytz>=2020.1 in c:\programdata\anaconda3\lib\site-packages (from pandas>=1.2->seaborn->missingno) (2023.3.post1) Requirement already satisfied: tzdata>=2022.1 in c:\programdata\anaconda3\lib\site-packages (from pandas>=1.2->seaborn->missingno) (2023.3) Requirement already satisfied: six>=1.5 in c:\programdata\anaconda3\lib\site-packages (from python-dateutil>=2.7->matplotlib->missingno) (1.16.0) Requirement already satisfied: folium in c:\programdata\anaconda3\lib\site-packages (0.16.0) Requirement already satisfied: branca>=0.6.0 in c:\programdata\anaconda3\lib\site-packages (from folium) (0.7.1) Requirement already satisfied: jinja2>=2.9 in c:\programdata\anaconda3\lib\site-packages (from folium) (3.1.3) Requirement already satisfied: numpy in c:\programdata\anaconda3\lib\site-packages (from folium) (1.26.4) Requirement already satisfied: requests in c:\programdata\anaconda3\lib\site-packages (from folium) (2.31.0) Requirement already satisfied: xyzservices in c:\programdata\anaconda3\lib\site-packages (from folium) (2022.9.0) Requirement already satisfied: MarkupSafe>=2.0 in c:\programdata\anaconda3\lib\site-packages (from jinja2>=2.9->folium) (2.1.3) Requirement already satisfied: charset-normalizer<4,>=2 in c:\programdata\anaconda3\lib\site-packages (from requests->folium) (2.0.4) Requirement already satisfied: idna<4,>=2.5 in c:\programdata\anaconda3\lib\site-packages (from requests->folium) (3.4) Requirement already satisfied: urllib3<3,>=1.21.1 in c:\programdata\anaconda3\lib\site-packages (from requests->folium) (2.0.7) Requirement already satisfied: certifi>=2017.4.17 in c:\programdata\anaconda3\lib\site-packages (from requests->folium) (2024.2.2)
#загрузка данных
data = pd.read_csv('moscow_places.csv')
#основная информация о датафрейме
data.info()
#вывод первых строк датафрейма
data.head(20)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 8406 entries, 0 to 8405 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 8406 non-null object 1 category 8406 non-null object 2 address 8406 non-null object 3 district 8406 non-null object 4 hours 7870 non-null object 5 lat 8406 non-null float64 6 lng 8406 non-null float64 7 rating 8406 non-null float64 8 price 3315 non-null object 9 avg_bill 3816 non-null object 10 middle_avg_bill 3149 non-null float64 11 middle_coffee_cup 535 non-null float64 12 chain 8406 non-null int64 13 seats 4795 non-null float64 dtypes: float64(6), int64(1), object(7) memory usage: 919.5+ KB
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.878494 | 37.478860 | 5.0 | NaN | NaN | NaN | NaN | 0 | NaN |
| 1 | Четыре комнаты | ресторан | Москва, улица Дыбенко, 36, корп. 1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.875801 | 37.484479 | 4.5 | выше среднего | Средний счёт:1500–1600 ₽ | 1550.0 | NaN | 0 | 4.0 |
| 2 | Хазри | кафе | Москва, Клязьминская улица, 15 | Северный административный округ | пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00–02:00 | 55.889146 | 37.525901 | 4.6 | средние | Средний счёт:от 1000 ₽ | 1000.0 | NaN | 0 | 45.0 |
| 3 | Dormouse Coffee Shop | кофейня | Москва, улица Маршала Федоренко, 12 | Северный административный округ | ежедневно, 09:00–22:00 | 55.881608 | 37.488860 | 5.0 | NaN | Цена чашки капучино:155–185 ₽ | NaN | 170.0 | 0 | NaN |
| 4 | Иль Марко | пиццерия | Москва, Правобережная улица, 1Б | Северный административный округ | ежедневно, 10:00–22:00 | 55.881166 | 37.449357 | 5.0 | средние | Средний счёт:400–600 ₽ | 500.0 | NaN | 1 | 148.0 |
| 5 | Sergio Pizza | пиццерия | Москва, Ижорская улица, вл8Б | Северный административный округ | ежедневно, 10:00–23:00 | 55.888010 | 37.509573 | 4.6 | средние | NaN | NaN | NaN | 0 | NaN |
| 6 | Огни города | бар,паб | Москва, Клязьминская улица, 9, стр. 3 | Северный административный округ | пн 15:00–04:00; вт-вс 15:00–05:00 | 55.890752 | 37.524653 | 4.4 | средние | Средний счёт:199 ₽ | 199.0 | NaN | 0 | 45.0 |
| 7 | Mr. Уголёк | быстрое питание | Москва, Клязьминская улица, 9, стр. 3 | Северный административный округ | пн-чт 10:00–22:00; пт,сб 10:00–23:00; вс 10:00–22:00 | 55.890636 | 37.524303 | 4.7 | средние | Средний счёт:200–300 ₽ | 250.0 | NaN | 0 | 45.0 |
| 8 | Donna Maria | ресторан | Москва, Дмитровское шоссе, 107, корп. 4 | Северный административный округ | ежедневно, 10:00–22:00 | 55.880045 | 37.539006 | 4.8 | средние | Средний счёт:от 500 ₽ | 500.0 | NaN | 0 | 79.0 |
| 9 | Готика | кафе | Москва, Ангарская улица, 39 | Северный административный округ | ежедневно, 12:00–00:00 | 55.879038 | 37.524487 | 4.3 | средние | Средний счёт:1000–1200 ₽ | 1100.0 | NaN | 0 | 65.0 |
| 10 | Great Room Bar | бар,паб | Москва, Левобережная улица, 12 | Северный административный округ | ежедневно, круглосуточно | 55.877832 | 37.469171 | 4.5 | средние | Цена бокала пива:250–350 ₽ | NaN | NaN | 0 | 102.0 |
| 11 | Шашлык Шефф | кафе | Москва, улица Маршала Федоренко, 10с1 | Северный административный округ | ежедневно, 10:00–21:00 | 55.881770 | 37.492362 | 4.9 | NaN | NaN | NaN | NaN | 0 | NaN |
| 12 | Заправка | кафе | Москва, МКАД, 80-й километр, 1 | Северный административный округ | вт-сб 09:00–18:00 | 55.899938 | 37.517958 | 4.3 | средние | Средний счёт:330 ₽ | 330.0 | NaN | 0 | NaN |
| 13 | Буханка | булочная | Москва, Базовская улица, 15, корп. 1 | Северный административный округ | ежедневно, 08:00–22:00 | 55.877007 | 37.504980 | 4.8 | NaN | NaN | NaN | NaN | 1 | 180.0 |
| 14 | У Сильвы | бар,паб | Москва, Ангарская улица, 42с1 | Северный административный округ | ежедневно, 13:00–00:00 | 55.885528 | 37.528371 | 4.2 | выше среднего | Средний счёт:1500 ₽ | 1500.0 | NaN | 0 | NaN |
| 15 | Дом обеда | столовая | Москва, улица Бусиновская Горка, 2 | Северный административный округ | пн-пт 08:30–18:30; сб 10:00–20:00 | 55.885890 | 37.493264 | 4.1 | средние | Средний счёт:300–500 ₽ | 400.0 | NaN | 0 | 180.0 |
| 16 | База Стритфуд | кафе | Москва, Базовская улица, 15, корп. 8 | Северный административный округ | ежедневно, 10:00–23:00 | 55.877859 | 37.507754 | 4.2 | средние | Средний счёт:140–350 ₽ | 245.0 | NaN | 0 | NaN |
| 17 | Чайхана Беш-Бармак | ресторан | Москва, Ленинградское шоссе, 71Б, стр. 2 | Северный административный округ | ежедневно, круглосуточно | 55.876908 | 37.449876 | 4.4 | средние | Средний счёт:350–500 ₽ | 425.0 | NaN | 0 | 96.0 |
| 18 | Час-Пик | столовая | Москва, Коровинское шоссе, 30А | Северный административный округ | ежедневно, 09:00–21:00 | 55.884651 | 37.517482 | 4.3 | средние | Средний счёт:200–300 ₽ | 250.0 | NaN | 0 | 25.0 |
| 19 | Пекарня | булочная | Москва, Ижорский проезд, 5 | Северный административный округ | ежедневно, круглосуточно | 55.887969 | 37.515688 | 4.4 | NaN | NaN | NaN | NaN | 1 | NaN |
Данные отображаются корректно.
В целях оптимизации обработки переменных столбец seats лучше привести к типу int - количество мест не может быть дробным. Также, если в столбцах middle_avg_bill и middle_coffee_cup только целые значения, то их также следует привести к типу int.
В датасете присутствуют пропуски в столбцах hours, price, avg_bill, middle_avg_bill, middle_coffee_cup, seats.
Необходимо проверить датафрейм на наличие дубликатов и пропусков, ошибочных значений, а также на отсутствие явных внутренних противоречий в данных.
Вспомогательные функции¶
#функция для вычисления первого и второго квартиля и межквартильного размаха
def q_iqr(series):
try:
#первый квартиль
q1 = np.percentile(
series,
25)
#третий квартиль
q3 = np.percentile(
series,
75)
#межквартильный размах
iqr = q3 - q1
return [q1, q3, iqr]
except:
return 'Ошибка работы функции'
#функция для вывода гистограммы, диаграммы размаха и основных характеристик столбца
def description(df, col, bins=100):
try:
plt.figure(figsize=(15,10))
#гистограмма распределения
ax1 = plt.subplot(2,1, 1)
ax1 = sns.histplot(df[col], bins=bins)
ax1.set_title('Гистограмма значений столбца')
ax1.set_xlabel('Значения')
ax1.set_ylabel('Частота')
#диаграмма размаха
ax2 = plt.subplot(2,1, 2, sharex=ax1)
ax2 = sns.violinplot(x=col, data=df)
ax2.set_title('Распределение значений и диаграмма размаха')
ax2.set_xlabel('Значения')
ax2.set_ylabel('Частота')
plt.show()
#основные характеристики
display('Основные характеристики столбца', df[col].describe())
except:
display('Ошибка работы функции')
#функция для визуализации категориальных данных
def cat_visualise(
data,
grouper,
grouper_name,
aggble_val,
aggfunc,
aggfunc_name):
try:
#сводная таблица
pivot = (
data.groupby(grouper, as_index=False)[aggble_val]
.agg(aggfunc)
.sort_values(by=aggble_val, ascending=False)
)
#диаграмма количества объектов общественного питания по категориям
plt.figure(figsize=(15,5))
ax = sns.barplot(
pivot,
x=aggble_val,
y=grouper
)
ax.bar_label(ax.containers[0], fontsize=12)
ax.set_title(f'{aggfunc_name} заведений, разбивка по: {grouper_name}')
ax.set_xlabel(aggfunc_name)
ax.set_ylabel(grouper_name);
#сохранение изображения
plt_name = pics_path + aggfunc_name + ' заведений, разбивка по ' + grouper_name + '.png'
plt.savefig(plt_name)
plt.show();
#сводная таблица для круговой диаграммы
pivot = pivot.sort_values(by=grouper)
#группировка секторов графика с малыми долями
low_share_groupers = []
for gr in pivot[grouper]:
if ((
pivot[pivot[grouper] == gr][aggble_val]
.iloc[0]
) / pivot[aggble_val].sum()) < 0.02:
low_share_groupers.append(gr)
if len(low_share_groupers) > 1:
sum = (
pivot[pivot[grouper]
.isin(low_share_groupers)][aggble_val]
.sum()
)
pivot.loc[len(pivot.index)] = ['остальные', sum]
pivot = pivot[~pivot[grouper].isin(low_share_groupers)]
#диаграмма долей объектов общественного питания по категориям
plt.figure(figsize=(8,8))
ax = plt.pie(
pivot[aggble_val],
labels=pivot[grouper],
autopct='%0.1f%%',
)
plt.title(f'{aggfunc_name} заведений. Соотношение по: {grouper_name}')
#сохранение изображения
plt_name = pics_path + aggfunc_name + '. Соотношение по ' + grouper_name + '.png'
plt.savefig(
plt_name,
bbox_inches= 'tight'
)
plt.show();
except:
display('Ошибка работы функции')
Функции для сравнения датафрейма, отфильтрованного по каким-либо признакам (например, топ-15 сетевых заведений по количеству заведений), с основным датафреймом (либо с датафреймом отфильтрованным по другому признаку).
Функция для сравнения по категориальному признаку:
- группирует датафреймы;
- вычисляет доли заведений для каждого значения признака;
- сравнивает датафреймы по этим долям;
- возвращает объединённую сводную таблицу с отклонениями в долях отфильтрованного датафрейма относительно исходного.
Функция для сравнения по количественному признаку вычисляет коэффициент корелляции и отклонение среднего отфильтрованного датафрейма относительно исходного по данному признаку.
#функция сравнения по категориальному признаку
def compare_cat(df, df_filt, col, threshold=20, aggfunc='count'):
try:
#группировка основного датафрейма по признаку и доли заведений по признаку
pivot_full = (
df
.groupby(col, as_index=False)['name']
.agg(aggfunc)
)
pivot_full['rate_full'] = (
pivot_full['name'] / pivot_full['name'].sum()
)
pivot_full.columns = [col, aggfunc + '_full', 'rate_full']
#группировка отфильтрованного датафрейма по признаку и доли заведений по признаку
pivot_filt = (
df_filt
.groupby(col, as_index=False)['name']
.agg(aggfunc)
)
pivot_filt['rate_filt'] = (
pivot_filt['name'] / pivot_filt['name'].sum()
)
pivot_filt.columns = [col, aggfunc + '_filtered', 'rate_filt']
#объединение
total = pivot_full.merge(
pivot_filt,
left_on=col,
right_on=col,
how='left'
)
#отклонение
total['deviation_abs'] = total['rate_full'] - total['rate_filt']
#отношение долей по признаку отфильтрованного и исходного датафреймов
total['deviation_percent'] = round(
(total['rate_filt'] / total['rate_full'] - 1) * 100,
1
)
#зададим порог значимого отклонения
total = total.query(' abs(deviation_percent) > @threshold')
return total
except:
print('Ошибка работы функции')
#функция сравнения по количественному признаку
def compare_numeric(df, df_filt, col):
try:
#удаление пропусков/заглушек
df = df[df[col] != -1]
df_filt = df_filt[df_filt[col] != -1]
#коэффициент корелляции
corr = df[col].corr(df_filt[col])
#отклонение среднего значения
full_mean = df[col].mean()
filt_mean = df_filt[col].mean()
deviation = filt_mean / full_mean - 1
return (
round(corr, 4),
round(deviation, 4),
round(full_mean,4),
round(filt_mean, 4)
)
except:
print('Ошибка работы функции')
Предобработка данных¶
Обработка дубликатов¶
Проверка на наличие явных дубликатов строк.
Явных дубликатов в датафрейме нет.
Однако в датасетах, содержащих столбцы с большим количеством строковых значений, могут присутствовать неявные дубликаты, в частности - столбцы с названиями: name, category, address, district. К этому могут приводить ошибки и опечатки при вводе, обработке и выгрузке данных из базы.
Проверим датасет на наличие таких дубликатов.
Прежде всего подсчитаем количество уникальных значений столбцов name, category, address, district.
for col in ['name', 'category', 'address', 'district']:
display(
f'Количество уникальных значений столбца {col}: {data[col].nunique()}',
)
'Количество уникальных значений столбца name: 5614'
'Количество уникальных значений столбца category: 8'
'Количество уникальных значений столбца address: 5753'
'Количество уникальных значений столбца district: 9'
Прежде всего проверим наличие дубликатов в столбцах category и district - это проще всего.
display(
f'значения столбца category: {data["category"].unique()}',
f'значения столбца district: {data["district"].unique()}'
)
"значения столбца category: ['кафе' 'ресторан' 'кофейня' 'пиццерия' 'бар,паб' 'быстрое питание'\n 'булочная' 'столовая']"
"значения столбца district: ['Северный административный округ'\n 'Северо-Восточный административный округ'\n 'Северо-Западный административный округ'\n 'Западный административный округ' 'Центральный административный округ'\n 'Восточный административный округ' 'Юго-Восточный административный округ'\n 'Южный административный округ' 'Юго-Западный административный округ']"
Дубликатов и ошибок в столбцах нет.
Для начала заменим в датафрейме буквы "ё" на "е" - это частая причина неявных дубликатов.
#замена ё на е
data['name'] = data['name'].str.replace('ё', 'е')
data['address'] = data['address'].str.replace('ё', 'е')
Далее, создадим для дальнейшей работы копию датафрейма data и приведём значения столбца name и address в этой копии к нижнему регистру.
Это позволит удалить из датасета результаты самых частых опечаток при вводе одинаковых данных.
#копия датафрейма
temp = data.copy()
#перевод в нижний регистр
temp['name'] = temp['name'].str.lower()
temp['address'] = temp['address'].str.lower()
#дубликаты строк в копии
temp.duplicated().sum()
0
Явных дубликатов нет.
Проверим наличие неявных дубликатов, рассмотрев строки, в которых совпадает имя и адрес - одноимённые заведения разного типа, расположенные по одному адресу, - нонсенс.
#строки, у которых совпадают имя, адрес
temp[temp[['name','address']].duplicated(keep=False)]
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 189 | кафе | кафе | москва, парк ангарские пруды | Северный административный округ | ежедневно, 09:00–23:00 | 55.880327 | 37.530786 | 3.2 | NaN | NaN | NaN | NaN | 0 | NaN |
| 215 | кафе | кафе | москва, парк ангарские пруды | Северный административный округ | ежедневно, 10:00–22:00 | 55.881438 | 37.531848 | 3.2 | NaN | NaN | NaN | NaN | 0 | NaN |
| 1430 | more poke | ресторан | москва, волоколамское шоссе, 11, стр. 2 | Северный административный округ | ежедневно, 09:00–21:00 | 55.806307 | 37.497566 | 4.2 | NaN | NaN | NaN | NaN | 0 | 188.0 |
| 1511 | more poke | ресторан | москва, волоколамское шоссе, 11, стр. 2 | Северный административный округ | пн-чт 09:00–18:00; пт,сб 09:00–21:00; вс 09:00–18:00 | 55.806307 | 37.497566 | 4.2 | NaN | NaN | NaN | NaN | 1 | 188.0 |
| 2211 | раковарня клешни и хвосты | ресторан | москва, проспект мира, 118 | Северо-Восточный административный округ | ежедневно, 12:00–00:00 | 55.810553 | 37.638161 | 4.4 | NaN | NaN | NaN | NaN | 0 | 150.0 |
| 2420 | раковарня клешни и хвосты | бар,паб | москва, проспект мира, 118 | Северо-Восточный административный округ | пн-чт 12:00–00:00; пт,сб 12:00–01:00; вс 12:00–00:00 | 55.810677 | 37.638379 | 4.4 | NaN | NaN | NaN | NaN | 1 | 150.0 |
| 3091 | хлеб да выпечка | булочная | москва, ярцевская улица, 19 | Западный административный округ | ежедневно, 09:00–22:00 | 55.738886 | 37.411648 | 4.1 | NaN | NaN | NaN | NaN | 1 | 276.0 |
| 3109 | хлеб да выпечка | кафе | москва, ярцевская улица, 19 | Западный административный округ | NaN | 55.738449 | 37.410937 | 4.1 | NaN | NaN | NaN | NaN | 0 | 276.0 |
Результат - четыре пары неявных дубликатов, по-видимому, внесённых в базу дважды. Удалим их из датафрейма.
temp = temp.drop(
index=temp[temp[['name','address']]
.duplicated()]
.index)
data = data[data.index.isin(temp.index)]
Другой возможный вариант неявных дубликатов - одинаковые названия, отличающиеся регистром одного или несколько символов. Проверим их наличие.
#создание списка названий, которые отличаются только регистром одного
#или нескольких символов - в нижнем регистре
list = (
pd.Series(data['name'].unique())
.str.lower()
.value_counts()
)
list = list.loc[list > 1]
list
чайхана бишкек сити 2
чудо печка 2
на углях 2
шашлык хаус 2
кофеin 2
..
abc coffee roasters 2
vabene 2
вкусвилл, кафе 2
домино'с пицца 2
бишкек сити 2
Name: count, Length: 101, dtype: int64
Неявные дубликаты данного типа есть, они образуют пары. Напишем простой код для их замены.
#создание списка имён оторые отличаются только регистром одного
#или нескольких символов - в том виде, в котором они содержатся в датафрейме
names_case_duplicates =(
pd.Series(
data['name']
.apply(lambda x: x if x.lower() in list.index else np.nan)
.dropna()
)
)
names_case_duplicates = names_case_duplicates.value_counts()
#создание словаря для замены дубликатов, отличающихся от оригиналов
#регистром одного или нескольких символов
dict = {}
for i in names_case_duplicates.index:
for j in names_case_duplicates.index:
if (i.lower() == j.lower()) & (i != j):
if (names_case_duplicates[i] >= names_case_duplicates[j]):
dict[j] = i
names_case_duplicates = names_case_duplicates.drop(index=i)
#вывод словаря
dict
{"Домино'с пицца": "Домино'с Пицца",
'One price coffee': 'One Price Coffee',
'Крошка картошка': 'Крошка Картошка',
'Хлеб насущный': 'Хлеб Насущный',
'МСК lounge': 'МСК Lounge',
'Wild bean cafe': 'Wild Bean Cafe',
'Правда Кофе': 'Правда кофе',
'Донер кебаб': 'Донер Кебаб',
'Кафе-Столовая': 'Кафе-столовая',
'Даблби': 'ДаблБи',
'Франклинс бургер': 'Франклинс Бургер',
'Star hit cafe': 'Star Hit Cafe',
'Ля фантази': 'Ля Фантази',
'Лепим и Варим': 'Лепим и варим',
'Burger club': 'Burger Club',
'Korean chick': 'Korean Chick',
'Чайхана Халяль': 'Чайхана халяль',
'Вкус востока': 'Вкус Востока',
'One&double': 'One&Double',
'Noba Coffee': 'Noba coffee',
'ABC coffee roasters': 'ABC Coffee Roasters',
'Рамен-клаб': 'Рамен-Клаб',
'Шаурма в Пите': 'Шаурма в пите',
'Pho street': 'Pho Street',
'ШашлычОК': 'Шашлычок',
'Халва, сеть почтоматов': 'Халва, Сеть Почтоматов',
'The wild bean cafe': 'The Wild Bean Cafe',
'Ача-чача': 'Ача-Чача',
'Чайхана Бишкек Сити': 'Чайхана Бишкек сити',
'Вкус Дня': 'Вкус дня',
'Raw to go': 'Raw to Go',
'РэдиМэйд': 'Рэдимэйд',
'BFL’S': 'Bfl’s',
'Deli2Go': 'Deli2go',
'Nova Bubble Tea': 'Nova bubble tea',
'FLIP': 'Flip',
'Poke House': 'Poke house',
'Wave california poke': 'Wave California Poke',
'Пирог хауз': 'Пирог Хауз',
'Sedelice': 'SeDelice',
'Как Дома': 'Как дома',
'Sova coffee': 'Sova Coffee',
'Coffee point': 'Coffee Point',
'Старый Город': 'Старый город',
'Донер хаус': 'Донер Хаус',
'Гриль хаус': 'Гриль Хаус',
'Ели сацебели': 'Ели Сацебели',
'Шашлык хаус': 'Шашлык Хаус',
'АвлаБар': 'Авлабар',
'Han Cook': 'Han cook',
'Bb grill': 'Bb Grill',
'Новатор Кофе': 'Новатор кофе',
'Чудо печка': 'Чудо Печка',
'Кафе шашлык': 'Кафе Шашлык',
'Чудо Тандыр': 'Чудо тандыр',
'More poke': 'More Poke',
'Нэм Нэм': 'Нэм нэм',
'Семейная пекарня': 'Семейная Пекарня',
'Домик в Саду': 'Домик в саду',
'Между Булок': 'Между булок',
'Ливан Хаус': 'Ливан хаус',
'Pasta cup & pizza': 'Pasta Cup & Pizza',
'Кофеin': 'КофеIN',
'Практика Кофе': 'Практика кофе',
'Ням-Ням': 'Ням-ням',
'ПаПан': 'Папан',
'На углях': 'На Углях',
'Гюмри Хауз': 'Гюмри хауз',
'Хлеб Насущный Экспресс': 'Хлеб Насущный экспресс',
'Хочу хычин': 'Хочу Хычин',
'Ма Ми': 'МА Ми',
'Espresso Bar': 'Espresso bar',
'Донер & Гриль': 'Донер & гриль',
'Токио рамен': 'Токио Рамен',
'Di Villaggio': 'Di villaggio',
'Центр Плова': 'Центр плова',
'Шашлык на углях': 'Шашлык на Углях',
'Греки здесь': 'Греки Здесь',
'Донер Бистро': 'Донер бистро',
'Вкусвилл, кафе': 'ВкусВилл, кафе',
'Раковарня Клешни и Хвосты': 'Раковарня Клешни и хвосты',
'I Like Wine': 'I like wine',
'Бишкек Сити': 'Бишкек сити',
'Вьет Лотос': 'Вьет лотос',
'Чайхона Ош сити': 'Чайхона ОШ Сити',
'Хинкальная Экспресс': 'Хинкальная экспресс',
'PiNzeria by Bontempi': 'Pinzeria by Bontempi',
'ГастроФерма': 'Гастроферма',
'Новая столовая': 'Новая Столовая',
'Кофе стоп': 'Кофе Стоп',
'Хинкали Point': 'Хинкали point',
'Важная Персона': 'Важная персона',
'Hellopapaya': 'HelloPapaya',
'Istanbul kebab': 'Istanbul Kebab',
'VABENE': 'Vabene',
'Советские Времена': 'Советские времена',
'Pomodoro royal': 'Pomodoro Royal',
'IL pizzaiolo': 'IL Pizzaiolo',
'Ku: рамен изакая бар': 'Ku: Рамен Изакая бар',
'Здоровое Питание': 'Здоровое питание',
'Carrots and Beans': 'Carrots and beans'}
#замена
data['name'] = data['name'].replace(dict)
Полных дубликатов строк нет, неявные дубликаты названий заведений исправлены.
Обработка пропусков¶
#диаграмма пропущенных значений
ax = msno.bar(data, figsize=(15,7))
ax.set_title(
'Соотношение корректных и пропущенных значений в столбцах\n',
fontsize=16
)
ax.set_xlabel('Столбцы', fontsize=16)
ax.set_ylabel('Значения', fontsize=16)
plt.show();
#вывод количества пропущенных значений для каждого столбца
data.isna().sum()
name 0 category 0 address 0 district 0 hours 535 lat 0 lng 0 rating 0 price 5087 avg_bill 4586 middle_avg_bill 5253 middle_coffee_cup 7867 chain 0 seats 3610 dtype: int64
Пропуски в столбце hours¶
В столбце hours 536 пропусков. Выведем эти строки датафрейма.
data[data['hours'].isna()]
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 38 | Ижора | булочная | Москва, Ижорский проезд, 5А | Северный административный округ | NaN | 55.888366 | 37.514856 | 4.4 | NaN | NaN | NaN | NaN | 0 | NaN |
| 40 | Кафе | кафе | Москва, Ижорская улица, 18, стр. 1 | Северный административный округ | NaN | 55.895115 | 37.524902 | 3.7 | NaN | NaN | NaN | NaN | 0 | NaN |
| 44 | Кафетерий | кафе | Москва, Ангарская улица, 24А | Северный административный округ | NaN | 55.876289 | 37.519315 | 3.8 | NaN | NaN | NaN | NaN | 1 | 8.0 |
| 56 | Рыба из тандыра | быстрое питание | Москва, Коровинское шоссе, 46, стр. 5 | Северный административный округ | NaN | 55.888010 | 37.515960 | 1.5 | NaN | NaN | NaN | NaN | 0 | NaN |
| 108 | Кафе | бар,паб | Москва, МКАД, 82-й километр, вл18 | Северо-Восточный административный округ | NaN | 55.908930 | 37.558777 | 4.2 | NaN | NaN | NaN | NaN | 0 | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 8236 | 1у | кафе | Москва, Нагатинская набережная, 40/1к1 | Южный административный округ | NaN | 55.685528 | 37.673546 | 3.4 | NaN | NaN | NaN | NaN | 0 | NaN |
| 8375 | Улица Гурьянова 55 | кафе | Москва, улица Гурьянова, 55 | Юго-Восточный административный округ | NaN | 55.679981 | 37.717034 | 4.5 | NaN | NaN | NaN | NaN | 0 | NaN |
| 8378 | Восточно-грузинская кухня | быстрое питание | Москва, Зеленодольская улица, 32, корп. 3 | Юго-Восточный административный округ | NaN | 55.710540 | 37.767864 | 4.3 | NaN | NaN | NaN | NaN | 0 | 120.0 |
| 8381 | Аэлита | кафе | Москва, Ферганская улица, 8, корп. 2, стр. 1 | Юго-Восточный административный округ | NaN | 55.708871 | 37.803831 | 3.8 | NaN | NaN | NaN | NaN | 0 | 30.0 |
| 8395 | Истира Запрафка | кафе | Москва, Юго-Восточный административный округ, Нижегородский район | Юго-Восточный административный округ | NaN | 55.724686 | 37.710558 | 2.9 | NaN | NaN | NaN | NaN | 0 | NaN |
535 rows × 14 columns
Удалять эти строки нельзя, потому что в них содержится прочая информация о заведениях, которая будет полезна для анализа - по меньшей мере в столбцах category, rating и chain, по которым будет проведена важная часть анализа. Во избежание проблем с обработкой данных этого столбца заменим пропуски значениями-заглушками.
data['hours'] = data['hours'].fillna('неизвестно')
Пропуски в столбце price¶
В столбце price 5087 пропусков (60,5%). Выведем эти строки датафрейма.
data[data['price'].isna()]
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.878494 | 37.478860 | 5.0 | NaN | NaN | NaN | NaN | 0 | NaN |
| 3 | Dormouse Coffee Shop | кофейня | Москва, улица Маршала Федоренко, 12 | Северный административный округ | ежедневно, 09:00–22:00 | 55.881608 | 37.488860 | 5.0 | NaN | Цена чашки капучино:155–185 ₽ | NaN | 170.0 | 0 | NaN |
| 11 | Шашлык Шефф | кафе | Москва, улица Маршала Федоренко, 10с1 | Северный административный округ | ежедневно, 10:00–21:00 | 55.881770 | 37.492362 | 4.9 | NaN | NaN | NaN | NaN | 0 | NaN |
| 13 | Буханка | булочная | Москва, Базовская улица, 15, корп. 1 | Северный административный округ | ежедневно, 08:00–22:00 | 55.877007 | 37.504980 | 4.8 | NaN | NaN | NaN | NaN | 1 | 180.0 |
| 19 | Пекарня | булочная | Москва, Ижорский проезд, 5 | Северный административный округ | ежедневно, круглосуточно | 55.887969 | 37.515688 | 4.4 | NaN | NaN | NaN | NaN | 1 | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 8401 | Суши Мания | кафе | Москва, Профсоюзная улица, 56 | Юго-Западный административный округ | ежедневно, 09:00–02:00 | 55.670021 | 37.552480 | 4.4 | NaN | NaN | NaN | NaN | 0 | 86.0 |
| 8402 | Миславнес | кафе | Москва, Пролетарский проспект, 19, корп. 1 | Южный административный округ | ежедневно, 08:00–22:00 | 55.640875 | 37.656553 | 4.8 | NaN | NaN | NaN | NaN | 0 | 150.0 |
| 8403 | Самовар | кафе | Москва, Люблинская улица, 112А, стр. 1 | Юго-Восточный административный округ | ежедневно, круглосуточно | 55.648859 | 37.743219 | 3.9 | NaN | Средний счёт:от 150 ₽ | 150.0 | NaN | 0 | 150.0 |
| 8404 | Чайхана Sabr | кафе | Москва, Люблинская улица, 112А, стр. 1 | Юго-Восточный административный округ | ежедневно, круглосуточно | 55.648849 | 37.743222 | 4.2 | NaN | NaN | NaN | NaN | 1 | 150.0 |
| 8405 | Kebab Time | кафе | Москва, Россошанский проезд, 6 | Южный административный округ | ежедневно, круглосуточно | 55.598229 | 37.604702 | 3.9 | NaN | NaN | NaN | NaN | 0 | 12.0 |
5087 rows × 14 columns
Выведем значения столбца и их количество.
data['price'].value_counts()
price средние 2117 выше среднего 564 высокие 478 низкие 156 Name: count, dtype: int64
display(
'Размеры части датафрейма, в которой есть пропуски в столбце **price** но нет пропусков в столбце **middle_avg_bill**:',
data.query(
'price.isna() & ~middle_avg_bill.isna()'
).shape
)
display(
'Размеры части датафрейма, в которой в обоих столбцах нет пропусков:**',
data.query(
'~price.isna() & ~middle_avg_bill.isna()'
).shape
)
'Размеры части датафрейма, в которой есть пропуски в столбце **price** но нет пропусков в столбце **middle_avg_bill**:'
(470, 14)
'Размеры части датафрейма, в которой в обоих столбцах нет пропусков:**'
(2679, 14)
Если взаимосвязь есть, можно заполнить до 470 пропусков (до 9,2%) в столбце. Данных для получения нужной для этого информации достаточно - 2679 строк (31,9% датафрейма).
Для оценки взаимосвязи построим график типа stripplot.
#срез датафрейма, в которой в обоих столбцах нет пропусков
query = data.query('~price.isna() & ~middle_avg_bill.isna()')
#график
plt.figure(figsize=(7,15))
ax = sns.stripplot(
data=query,
x='price',
y='middle_avg_bill',
color='steelblue',
alpha=0.6)
ax.set_title(
'Распределение значений среднего чека по категориям столбца price'
)
ax.set_xlabel('Категории')
ax.set_ylabel('Средний чек')
plt.show();
Определённая взаимосвязь есть, но сначала нужно очистить наборы значений от выбросов.
Отобразим выбросы наглядно, построив график типа boxplot.
#график
plt.figure(figsize=(7,15))
ax = sns.boxplot(x='price', y='middle_avg_bill', data=data)
ax.set_title(
'Распределение значений среднего чека по категориям столбца price'
)
ax.set_xlabel('Категории')
ax.set_ylabel('Средний чек')
plt.show();
Очистим наборы от выбросов.
#цикл для удаления выбросов
for cat in query['price'].unique():
#временная переменная
temp = query[query['price'] == cat]['middle_avg_bill']
iqrs = q_iqr(temp)
#удаление выбросов в категории и замена их пропусками
query.loc[query['price'] == cat, 'middle_avg_bill'] = \
temp.map(
lambda x: x if (iqrs[0] - 1.5 * iqrs[2] <= x) & \
(x <= iqrs[1] + 1.5 * iqrs[2]) else np.nan
)
#удаление пропусков
query = query.query('~middle_avg_bill.isna()')
query.shape
(2619, 14)
Удалено 60 значений-выбросов (2,2%).
Построим график типа stripplot без выбросов.
#график
plt.figure(figsize=(7,15))
ax = sns.stripplot(
data=query,
x='price',
y='middle_avg_bill',
color='steelblue',
alpha=0.6
)
ax.set_title('Распределение значений среднего чека по категориям столбца price')
ax.set_xlabel('Категории')
ax.set_ylabel('Средний чек')
plt.axhline(
y=query[query['price'] == 'высокие']['middle_avg_bill'].min(),
linestyle='--'
)
plt.axhline(
y=query[query['price'] == 'средние']['middle_avg_bill'].min(),
linestyle='--'
)
plt.axhline(
y=query[query['price'] == 'низкие']['middle_avg_bill'].max(),
linestyle='--'
)
plt.axhline(
y=query[query['price'] == 'выше среднего']['middle_avg_bill'].max(),
linestyle='--'
)
plt.show();
Диапазон значений категории "выше среднего" полностью перекрывается диапазоном значений категории "высокие", однако диапазоны категорий "высокие", "средние" и "низкие" включают значения, которые для них уникальны. Это значит, что можно заполнить часть пропусков в столбце price, соответствующие этим наборам уникальных значений.
#замена пропусков для диапазона значений среднего чека, соответствующих низким
data.loc[
(
data['price'].isna() &
(data['middle_avg_bill'] < \
query[query['price'] == 'средние']['middle_avg_bill'].min()) &
~data['middle_avg_bill'].isna()
),
'price'
] = 'низкие'
#замена пропусков для диапазона значений среднего чека, соответствующих средним
data.loc[
(
data['price'].isna() &
(data['middle_avg_bill'] > \
query[query['price'] == 'низкие']['middle_avg_bill'].max()) &
(data['middle_avg_bill'] < \
query[query['price'] == 'высокие']['middle_avg_bill'].min()) &
~data['middle_avg_bill'].isna()
),
'price'
] = 'средние'
#замена пропусков для диапазона значений среднего чека, соответствующих высоким
data.loc[
(
data['price'].isna() &
(data['middle_avg_bill'] > (
query[query['price'] == 'выше среднего']['middle_avg_bill']
.max()
)) &
~data['middle_avg_bill'].isna()
),
'price'
] = 'высокие'
#оставшееся количество пропусков
data[data['price'].isna()].shape
(4890, 14)
Удалось заполнить 197 пропусков.
Оценим размеры той части датафрейма, в которой есть пропуски в столбце price но нет пропусков в столбце middle_coffee_cup (часть которой будем заполнять), а также ту часть, в которой в обоих столбцах нет пропусков (часть, из которой возьмём данные для заполнения), построим график stripplot.
display(
'Размеры части датафрейма, в которой есть пропуски в столбце **price** но нет пропусков в столбце **middle_avg_bill**:',
data.query('price.isna() & ~middle_coffee_cup.isna()').shape
)
display(
'Размеры части датафрейма, в которой в обоих столбцах нет пропусков:',
data.query('~price.isna() & ~middle_coffee_cup.isna()').shape
)
'Размеры части датафрейма, в которой есть пропуски в столбце **price** но нет пропусков в столбце **middle_avg_bill**:'
(257, 14)
'Размеры части датафрейма, в которой в обоих столбцах нет пропусков:'
(278, 14)
#график
plt.figure(figsize=(7,15))
ax = sns.stripplot(
data=query,
x='price',
y='middle_avg_bill',
color='steelblue',
alpha=0.6
)
ax.set_title('Распределение значений среднего чека по категориям столбца price')
ax.set_xlabel('Категории')
ax.set_ylabel('Средний чек')
plt.axhline(y=query[query['price'] == 'высокие']['middle_avg_bill'].min(), linestyle='--')
plt.axhline(y=query[query['price'] == 'средние']['middle_avg_bill'].min(), linestyle='--')
plt.axhline(y=query[query['price'] == 'низкие']['middle_avg_bill'].max(), linestyle='--')
plt.axhline(y=query[query['price'] == 'выше среднего']['middle_avg_bill'].max(), linestyle='--')
plt.show();
#срез датафрейма, в которой в обоих столбцах нет пропусков
query = data.query('~price.isna() & ~middle_coffee_cup.isna()')
#график
plt.figure(figsize=(7,15))
ax = sns.stripplot(
data=query,
x='price',
y='middle_coffee_cup',
color='steelblue',
alpha=0.6
)
ax.set_title('Распределение значений средней цены чашки кофе по категориям столбца price')
ax.set_xlabel('Категории')
ax.set_ylabel('Средний чек')
plt.show();
Проследить явную взаимосвязь между значениями столбцов не удаётся: возможно, её нет, либо данных для этого недостаточно.
Оставшиеся в столбце пропуски заполнить нет возможности. Во избежание проблем с обработкой данных этого столбца заменим пропуски значениями-заглушками.
data['price'] = data['price'].fillna('неизвестно')
Пропуски в столбце avg_bill¶
В столбце avg_bill 4586 пропусков (54,6%). Выведем эти строки датафрейма.
data[data['avg_bill'].isna()]
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.878494 | 37.478860 | 5.0 | неизвестно | NaN | NaN | NaN | 0 | NaN |
| 5 | Sergio Pizza | пиццерия | Москва, Ижорская улица, вл8Б | Северный административный округ | ежедневно, 10:00–23:00 | 55.888010 | 37.509573 | 4.6 | средние | NaN | NaN | NaN | 0 | NaN |
| 11 | Шашлык Шефф | кафе | Москва, улица Маршала Федоренко, 10с1 | Северный административный округ | ежедневно, 10:00–21:00 | 55.881770 | 37.492362 | 4.9 | неизвестно | NaN | NaN | NaN | 0 | NaN |
| 13 | Буханка | булочная | Москва, Базовская улица, 15, корп. 1 | Северный административный округ | ежедневно, 08:00–22:00 | 55.877007 | 37.504980 | 4.8 | неизвестно | NaN | NaN | NaN | 1 | 180.0 |
| 19 | Пекарня | булочная | Москва, Ижорский проезд, 5 | Северный административный округ | ежедневно, круглосуточно | 55.887969 | 37.515688 | 4.4 | неизвестно | NaN | NaN | NaN | 1 | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 8400 | Практика кофе | кофейня | Москва, Чонгарский бульвар, 26А, корп. 1 | Юго-Западный административный округ | пн-пт 08:00–22:00; сб,вс 09:00–22:00 | 55.654289 | 37.600722 | 4.4 | неизвестно | NaN | NaN | NaN | 0 | 55.0 |
| 8401 | Суши Мания | кафе | Москва, Профсоюзная улица, 56 | Юго-Западный административный округ | ежедневно, 09:00–02:00 | 55.670021 | 37.552480 | 4.4 | неизвестно | NaN | NaN | NaN | 0 | 86.0 |
| 8402 | Миславнес | кафе | Москва, Пролетарский проспект, 19, корп. 1 | Южный административный округ | ежедневно, 08:00–22:00 | 55.640875 | 37.656553 | 4.8 | неизвестно | NaN | NaN | NaN | 0 | 150.0 |
| 8404 | Чайхана Sabr | кафе | Москва, Люблинская улица, 112А, стр. 1 | Юго-Восточный административный округ | ежедневно, круглосуточно | 55.648849 | 37.743222 | 4.2 | неизвестно | NaN | NaN | NaN | 1 | 150.0 |
| 8405 | Kebab Time | кафе | Москва, Россошанский проезд, 6 | Южный административный округ | ежедневно, круглосуточно | 55.598229 | 37.604702 | 3.9 | неизвестно | NaN | NaN | NaN | 0 | 12.0 |
4586 rows × 14 columns
Удалять эти строки нельзя, потому что в них содержится прочая информация о заведениях, которая будет полезна для анализа - по меньшей мере в столбцах category, rating и chain, по которым будет проведена важная часть анализа, к тому же их слишком много. Во избежание проблем с обработкой данных этого столбца заменим пропуски значениями-заглушками.полнить эти пропуски синтетическими данными нет возможности. Ставить значения-заглушки также нет необходимости, поэтому данные пропуски можно оставить без обработки.
data['avg_bill'] = data['avg_bill'].fillna('неизвестно')
Пропуски в столбце middle_avg_bill¶
В столбце middle_avg_bill 5253 пропуска (62,5%). Выведем эти строки датафрейма.
data[data['middle_avg_bill'].isna()]
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.878494 | 37.478860 | 5.0 | неизвестно | неизвестно | NaN | NaN | 0 | NaN |
| 3 | Dormouse Coffee Shop | кофейня | Москва, улица Маршала Федоренко, 12 | Северный административный округ | ежедневно, 09:00–22:00 | 55.881608 | 37.488860 | 5.0 | неизвестно | Цена чашки капучино:155–185 ₽ | NaN | 170.0 | 0 | NaN |
| 5 | Sergio Pizza | пиццерия | Москва, Ижорская улица, вл8Б | Северный административный округ | ежедневно, 10:00–23:00 | 55.888010 | 37.509573 | 4.6 | средние | неизвестно | NaN | NaN | 0 | NaN |
| 10 | Great Room Bar | бар,паб | Москва, Левобережная улица, 12 | Северный административный округ | ежедневно, круглосуточно | 55.877832 | 37.469171 | 4.5 | средние | Цена бокала пива:250–350 ₽ | NaN | NaN | 0 | 102.0 |
| 11 | Шашлык Шефф | кафе | Москва, улица Маршала Федоренко, 10с1 | Северный административный округ | ежедневно, 10:00–21:00 | 55.881770 | 37.492362 | 4.9 | неизвестно | неизвестно | NaN | NaN | 0 | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 8400 | Практика кофе | кофейня | Москва, Чонгарский бульвар, 26А, корп. 1 | Юго-Западный административный округ | пн-пт 08:00–22:00; сб,вс 09:00–22:00 | 55.654289 | 37.600722 | 4.4 | неизвестно | неизвестно | NaN | NaN | 0 | 55.0 |
| 8401 | Суши Мания | кафе | Москва, Профсоюзная улица, 56 | Юго-Западный административный округ | ежедневно, 09:00–02:00 | 55.670021 | 37.552480 | 4.4 | неизвестно | неизвестно | NaN | NaN | 0 | 86.0 |
| 8402 | Миславнес | кафе | Москва, Пролетарский проспект, 19, корп. 1 | Южный административный округ | ежедневно, 08:00–22:00 | 55.640875 | 37.656553 | 4.8 | неизвестно | неизвестно | NaN | NaN | 0 | 150.0 |
| 8404 | Чайхана Sabr | кафе | Москва, Люблинская улица, 112А, стр. 1 | Юго-Восточный административный округ | ежедневно, круглосуточно | 55.648849 | 37.743222 | 4.2 | неизвестно | неизвестно | NaN | NaN | 1 | 150.0 |
| 8405 | Kebab Time | кафе | Москва, Россошанский проезд, 6 | Южный административный округ | ежедневно, круглосуточно | 55.598229 | 37.604702 | 3.9 | неизвестно | неизвестно | NaN | NaN | 0 | 12.0 |
5253 rows × 14 columns
Заполнить пропуски в этом столбце можно только одним способом: если по какой-то причине в столбце avg_bill остались данные, не использованные для расчёта среднего счёта. Проверим это.
(
data[data['avg_bill']
.str.match('Средний счёт')]['middle_avg_bill']
.isna()
.sum()
)
0
Таких строк нет.
Удалять строки с пропусками в этом столбце нельзя, потому что в них содержится прочая информация о заведениях, которая будет полезна для анализа - по меньшей мере в столбцах category, rating и chain, по которым будет проведена важная часть анализа, к тому же их слишком много. Во избежание проблем с обработкой данных этого столбца заменим пропуски значениями-заглушками.
data['middle_avg_bill'] = data['middle_avg_bill'].fillna(-1)
Пропуски в столбце middle_coffee_cup¶
В столбце middle_coffee_cup 7867 пропусков (93,6%). Выведем эти строки датафрейма.
data[data['middle_coffee_cup'].isna()]
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.878494 | 37.478860 | 5.0 | неизвестно | неизвестно | -1.0 | NaN | 0 | NaN |
| 1 | Четыре комнаты | ресторан | Москва, улица Дыбенко, 36, корп. 1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.875801 | 37.484479 | 4.5 | выше среднего | Средний счёт:1500–1600 ₽ | 1550.0 | NaN | 0 | 4.0 |
| 2 | Хазри | кафе | Москва, Клязьминская улица, 15 | Северный административный округ | пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00–02:00 | 55.889146 | 37.525901 | 4.6 | средние | Средний счёт:от 1000 ₽ | 1000.0 | NaN | 0 | 45.0 |
| 4 | Иль Марко | пиццерия | Москва, Правобережная улица, 1Б | Северный административный округ | ежедневно, 10:00–22:00 | 55.881166 | 37.449357 | 5.0 | средние | Средний счёт:400–600 ₽ | 500.0 | NaN | 1 | 148.0 |
| 5 | Sergio Pizza | пиццерия | Москва, Ижорская улица, вл8Б | Северный административный округ | ежедневно, 10:00–23:00 | 55.888010 | 37.509573 | 4.6 | средние | неизвестно | -1.0 | NaN | 0 | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 8401 | Суши Мания | кафе | Москва, Профсоюзная улица, 56 | Юго-Западный административный округ | ежедневно, 09:00–02:00 | 55.670021 | 37.552480 | 4.4 | неизвестно | неизвестно | -1.0 | NaN | 0 | 86.0 |
| 8402 | Миславнес | кафе | Москва, Пролетарский проспект, 19, корп. 1 | Южный административный округ | ежедневно, 08:00–22:00 | 55.640875 | 37.656553 | 4.8 | неизвестно | неизвестно | -1.0 | NaN | 0 | 150.0 |
| 8403 | Самовар | кафе | Москва, Люблинская улица, 112А, стр. 1 | Юго-Восточный административный округ | ежедневно, круглосуточно | 55.648859 | 37.743219 | 3.9 | низкие | Средний счёт:от 150 ₽ | 150.0 | NaN | 0 | 150.0 |
| 8404 | Чайхана Sabr | кафе | Москва, Люблинская улица, 112А, стр. 1 | Юго-Восточный административный округ | ежедневно, круглосуточно | 55.648849 | 37.743222 | 4.2 | неизвестно | неизвестно | -1.0 | NaN | 1 | 150.0 |
| 8405 | Kebab Time | кафе | Москва, Россошанский проезд, 6 | Южный административный округ | ежедневно, круглосуточно | 55.598229 | 37.604702 | 3.9 | неизвестно | неизвестно | -1.0 | NaN | 0 | 12.0 |
7867 rows × 14 columns
Проверим соответствие значений столбца middle_coffee_cup и значений столбца avg_bill.
(
data[data['avg_bill']
.str.match('Цена чашки капучино')]['middle_coffee_cup']
.isna()
.sum()
)
0
Пропуски в столбцах соотносятся.
Удалять строки с пропусками в этом столбце нельзя, потому что в них содержится прочая информация о заведениях, которая будет полезна для анализа - по меньшей мере в столбцах category, rating и chain, по которым будет проведена важная часть анализа, к тому же их слишком много. Во избежание проблем с обработкой данных этого столбца заменим пропуски значениями-заглушками.
data['middle_coffee_cup'] = data['middle_coffee_cup'].fillna(-1)
Пропуски в столбце seats¶
В столбце seats 3610 пропусков (42,97%). Выведем эти строки датафрейма.
data[data['seats'].isna()]
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.878494 | 37.478860 | 5.0 | неизвестно | неизвестно | -1.0 | -1.0 | 0 | NaN |
| 3 | Dormouse Coffee Shop | кофейня | Москва, улица Маршала Федоренко, 12 | Северный административный округ | ежедневно, 09:00–22:00 | 55.881608 | 37.488860 | 5.0 | неизвестно | Цена чашки капучино:155–185 ₽ | -1.0 | 170.0 | 0 | NaN |
| 5 | Sergio Pizza | пиццерия | Москва, Ижорская улица, вл8Б | Северный административный округ | ежедневно, 10:00–23:00 | 55.888010 | 37.509573 | 4.6 | средние | неизвестно | -1.0 | -1.0 | 0 | NaN |
| 11 | Шашлык Шефф | кафе | Москва, улица Маршала Федоренко, 10с1 | Северный административный округ | ежедневно, 10:00–21:00 | 55.881770 | 37.492362 | 4.9 | неизвестно | неизвестно | -1.0 | -1.0 | 0 | NaN |
| 12 | Заправка | кафе | Москва, МКАД, 80-й километр, 1 | Северный административный округ | вт-сб 09:00–18:00 | 55.899938 | 37.517958 | 4.3 | средние | Средний счёт:330 ₽ | 330.0 | -1.0 | 0 | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 8387 | Pab&burg | бар,паб | Москва, улица Михайлова, 22, корп. 4 | Юго-Восточный административный округ | ежедневно, 12:00–21:30 | 55.726303 | 37.769842 | 4.2 | средние | Цена бокала пива:от 140 ₽ | -1.0 | -1.0 | 0 | NaN |
| 8389 | Assa | ресторан | Москва, улица Авиаконструктора Миля, 3А | Юго-Восточный административный округ | пн-чт 09:00–22:00; пт,сб 09:00–23:00; вс 09:00–22:00 | 55.686817 | 37.851225 | 4.9 | неизвестно | неизвестно | -1.0 | -1.0 | 0 | NaN |
| 8392 | Касабланка | кафе | Москва, Большая Косинская улица, 27 | Восточный административный округ | пн-чт 08:00–17:00; пт 08:00–16:00 | 55.718514 | 37.857576 | 3.3 | неизвестно | неизвестно | -1.0 | -1.0 | 0 | NaN |
| 8394 | Намангале | кафе | Москва, Ферганская улица, вл17-21 | Юго-Восточный административный округ | ежедневно, круглосуточно | 55.705332 | 37.819244 | 4.3 | неизвестно | неизвестно | -1.0 | -1.0 | 0 | NaN |
| 8395 | Истира Запрафка | кафе | Москва, Юго-Восточный административный округ, Нижегородский район | Юго-Восточный административный округ | неизвестно | 55.724686 | 37.710558 | 2.9 | неизвестно | неизвестно | -1.0 | -1.0 | 0 | NaN |
3610 rows × 14 columns
Заполнить эти пропуски нет возможности. Удалять строки с пропусками в этом столбце нельзя, потому что в них содержится прочая информация о заведениях, которая будет полезна для анализа - по меньшей мере в столбцах category, rating и chain, по которым будет проведена важная часть анализа, к тому же их слишком много. Во избежание проблем с обработкой данных этого столбца заменим пропуски значениями-заглушками.
data['seats'] = data['seats'].fillna(-1)
Вывод по разделу обработки пропусков¶
Удалось частично заполнить пропуски в столбце price. На пропуски в остальных столбцах установлены значения-заглушки.
#проверка
data.isna().sum()
name 0 category 0 address 0 district 0 hours 0 lat 0 lng 0 rating 0 price 0 avg_bill 0 middle_avg_bill 0 middle_coffee_cup 0 chain 0 seats 0 dtype: int64
Изменение типов данных¶
В целях оптимизации обработки переменных столбец seats лучше привести к типу int - количество мест не может быть дробным. Также, если в столбцах middle_avg_bill и middle_coffee_cup только целые значения, то их также следует привести к типу int.
#проверка наличия дробных значений в столбцах **middle_avg_bill** и **middle_coffee_cup**
display(
data[data['middle_avg_bill'] % 1 > 0]['middle_avg_bill'].count(),
data[data['middle_coffee_cup'] % 1 > 0]['middle_coffee_cup'].count()
)
0
0
#приведение к типу int
data[['seats','middle_avg_bill','middle_coffee_cup']] = \
data[['seats','middle_avg_bill','middle_coffee_cup']].astype(int)
Проверка столбцов на наличие ошибок и аномалий¶
Столбец category.¶
#вывод значений столбца и их количества
data['category'].value_counts()
category кафе 2376 ресторан 2042 кофейня 1413 бар,паб 764 пиццерия 633 быстрое питание 603 столовая 315 булочная 256 Name: count, dtype: int64
Столбец district.¶
data['district'].value_counts()
district Центральный административный округ 2242 Северный административный округ 898 Южный административный округ 892 Северо-Восточный административный округ 890 Западный административный округ 850 Восточный административный округ 798 Юго-Восточный административный округ 714 Юго-Западный административный округ 709 Северо-Западный административный округ 409 Name: count, dtype: int64
Ошибок нет.
Столбец avg_bill.
(
data['avg_bill']
.apply(lambda x: x[:x.find(':')] if (x !='неизвестно') else x)
.unique()
)
array(['неизвестно', 'Средний счёт', 'Цена чашки капучино',
'Цена бокала пива'], dtype=object)
Ошибок нет.
Столбец lat.¶
description(data, 'lat')
'Основные характеристики столбца'
count 8402.000000 mean 55.750080 std 0.069654 min 55.573942 25% 55.704950 50% 55.753401 75% 55.795017 max 55.928943 Name: lat, dtype: float64
Ошибок, аномалий и выбросов нет.
Столбец lng.
description(data, 'lng')
'Основные характеристики столбца'
count 8402.000000 mean 37.608613 std 0.098585 min 37.355651 25% 37.538653 50% 37.605263 75% 37.664819 max 37.874466 Name: lng, dtype: float64
Ошибок, аномалий и выбросов нет.
Столбец rating.
description(
data,
'rating',
bins=data['rating']
.nunique()
)
'Основные характеристики столбца'
count 8402.000000 mean 4.230017 std 0.470320 min 1.000000 25% 4.100000 50% 4.300000 75% 4.400000 max 5.000000 Name: rating, dtype: float64
Ошибочных значений нет. Значения менее 2, очевидно, являются не ошибками, а относятся к редким заведениям с низкой оценкой, их лучше сохранить для анализа и визулизации, учитывая, что они слабо влияют на статистику (разница между средним и медианой менее 2%).
Столбец middle_avg_bill.¶
description(data.query('middle_avg_bill != -1'), 'middle_avg_bill')
'Основные характеристики столбца'
count 3149.000000 mean 958.053668 std 1009.732845 min 0.000000 25% 375.000000 50% 750.000000 75% 1250.000000 max 35000.000000 Name: middle_avg_bill, dtype: float64
В столбце есть нулевые значения. Изучим их.
data.query('middle_avg_bill == 0')
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 3688 | Кофемания | кофейня | Москва, улица Новый Арбат, 19 | Центральный административный округ | ежедневно, круглосуточно | 55.752136 | 37.587784 | 4.5 | высокие | Средний счёт:от 0 ₽ | 0 | -1 | 1 | 200 |
Это явная ошибка. Удалим ошибочные значения из столбца и заменим их значениями-заглушками.
data.loc[data['middle_avg_bill'] == 0, 'middle_avg_bill'] = -1
Далее, изучим аномально большие выбросы.
#получение квартилей
iqrs = q_iqr(
data.query('middle_avg_bill != -1')['middle_avg_bill']
)
#срез с высоким средним чеком
abn_avg_bill = data.query(
'middle_avg_bill > @iqrs[1] + 1.5* @iqrs[2]'
)
description(abn_avg_bill, 'middle_avg_bill')
'Основные характеристики столбца'
count 104.000000 mean 4052.403846 std 3363.260812 min 2600.000000 25% 3000.000000 50% 3250.000000 75% 4000.000000 max 35000.000000 Name: middle_avg_bill, dtype: float64
104 строки с аномально высоким средним чеком. Возможно, речь об элитных заведениях. Проверим это.
abn_avg_bill['price'].value_counts()
price высокие 103 выше среднего 1 Name: count, dtype: int64
Так и есть. Почти все заведения относятся к категории "выше среднего".
Совершенно точно выбиваются значения среднего счёта более 10000. Изучим эту часть датафрейма.
data.query('middle_avg_bill >= 10000')
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 730 | Чойхона | бар,паб | Москва, Дмитровское шоссе, 95А | Северный административный округ | ежедневно, 10:00–23:00 | 55.871497 | 37.543555 | 4.4 | высокие | Средний счёт:5000–17000 ₽ | 11000 | -1 | 0 | 49 |
| 5481 | Гости | ресторан | Москва, шоссе Энтузиастов, 52 | Восточный административный округ | пн,вс 18:00–22:30 | 55.759088 | 37.760570 | 4.1 | высокие | Средний счёт:5000–15000 ₽ | 10000 | -1 | 0 | -1 |
| 7177 | Кафе | ресторан | Москва, Каширское шоссе, 23, стр. 2 | Южный административный округ | ежедневно, круглосуточно | 55.657450 | 37.646665 | 4.1 | высокие | Средний счёт:20000–50000 ₽ | 35000 | -1 | 0 | 100 |
Разброс значений среднего счёта в столбце avg_bill слишком большой, похоже на ошибку. Даже если это не ошибка, данные выбросы повлияют на статистику. Такие значения лучше заменить на заглушки.
data.loc[
data['middle_avg_bill'] >= 10000,
'middle_avg_bill'
] = -1
description(
data.query('middle_avg_bill != -1'),
'middle_avg_bill',
bins=50
)
'Основные характеристики столбца'
count 3145.000000 mean 941.466137 std 770.467416 min 30.000000 25% 375.000000 50% 750.000000 75% 1250.000000 max 7250.000000 Name: middle_avg_bill, dtype: float64
На гистограмме всё ещё присутствует длинный "хвост" редких высоких значений. Удалять их нельзя, поскольку они отражают элитный сегмент рынка.
Столбец middle_coffee_cup.¶
description(
data.query('middle_coffee_cup != -1'),
'middle_coffee_cup'
)
'Основные характеристики столбца'
count 535.000000 mean 174.721495 std 88.951103 min 60.000000 25% 124.500000 50% 169.000000 75% 225.000000 max 1568.000000 Name: middle_coffee_cup, dtype: float64
Изучим выбросы.
#получение квартилей
iqrs = q_iqr(
data.query(
'middle_coffee_cup != -1'
)['middle_coffee_cup']
)
#срез с высоким средним чеком
abn_coffee_cup = data.query(
'middle_coffee_cup >= @iqrs[1] + 1.5* @iqrs[2]'
)
abn_coffee_cup
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2859 | Шоколадница | кофейня | Москва, Большая Семеновская улица, 27, корп. 1 | Восточный административный округ | ежедневно, 08:00–23:00 | 55.782268 | 37.709022 | 4.2 | средние | Цена чашки капучино:230–2907 ₽ | -1 | 1568 | 1 | 48 |
Здесь явная ошибка, скорее всего, опечатка. Заменим выброс значением-заглушкой.
data.loc[
data['middle_coffee_cup'] == abn_coffee_cup['middle_coffee_cup'][2859],
'middle_coffee_cup'
] = -1
description(data.query('middle_coffee_cup != -1'), 'middle_coffee_cup')
'Основные характеристики столбца'
count 534.000000 mean 172.112360 std 65.408333 min 60.000000 25% 124.250000 50% 168.000000 75% 225.000000 max 375.000000 Name: middle_coffee_cup, dtype: float64
Столбец готов к анализу.
Столбец chain¶
#соотношение сетевых и не сетевых заведений
data['chain'].value_counts()
chain 0 5199 1 3203 Name: count, dtype: int64
Проверим, нет ли заведений со нулевым значением в столбце chain, одноимённых с сетевыми.
(
pd.Series(data[data['chain'] == 0]['name'].unique())
.isin(
data[data['chain'] == 1]['name']
.unique())
.sum()
)
40
Сорок наименований. Выведем список этих названий и количество сетевых и несетевых заведений с таким названием.
#список названий
names = (data
.groupby('name', as_index=False)['chain']
.agg('nunique'))
names[names['chain'] > 1]['name']
#сводная таблица
pivot_names = (
data[data['name']
.isin(names[names['chain'] > 1]['name'])]
.pivot_table(
index='name',
values='address',
columns='chain',
aggfunc='count'
)
)
pivot_names
| chain | 0 | 1 |
|---|---|---|
| name | ||
| ABC Coffee Roasters | 1 | 4 |
| Burger Club | 1 | 5 |
| Coffee Point | 1 | 2 |
| Flip | 1 | 2 |
| Korean Chick | 1 | 5 |
| More Poke | 1 | 1 |
| Noba coffee | 1 | 4 |
| Nova bubble tea | 1 | 2 |
| One Price Coffee | 1 | 71 |
| One&Double | 1 | 4 |
| Pho Street | 1 | 3 |
| Poke house | 1 | 2 |
| Raw to Go | 1 | 2 |
| SeDelice | 1 | 2 |
| Sova Coffee | 1 | 2 |
| Star Hit Cafe | 1 | 6 |
| Wave California Poke | 1 | 2 |
| Ача-Чача | 1 | 3 |
| Гриль Хаус | 1 | 2 |
| Домино'с Пицца | 1 | 76 |
| Ели Сацебели | 1 | 2 |
| Кафе-столовая | 9 | 2 |
| Ливан хаус | 1 | 1 |
| Ля Фантази | 1 | 5 |
| МСК Lounge | 1 | 14 |
| Матрешка | 1 | 2 |
| Огонек | 1 | 2 |
| Перекресток | 1 | 2 |
| Пирог Хауз | 1 | 2 |
| Раковарня Клешни и хвосты | 1 | 1 |
| Рамен-Клаб | 1 | 4 |
| Рэдимэйд | 1 | 2 |
| Старый город | 1 | 2 |
| Франклинс Бургер | 1 | 8 |
| Халва, Сеть Почтоматов | 1 | 3 |
| Хинкальная экспресс | 1 | 1 |
| Чайхана Бишкек сити | 1 | 2 |
| Шаурма в пите | 1 | 3 |
| Шашлык Хаус | 1 | 2 |
| Шашлычок | 1 | 3 |
Кафе-столовая - частое название, почти нарицательное; вполне возможно, что есть и отдельные заведения с таким названием, и небольшая сеть.
Во всех очтальных случаях, по-видимому ошибка в данных. Исправим её.
pivot_names = pivot_names.drop(index='Кафе-столовая')
data.loc[
data['name'].isin(pivot_names.index),
'chain'
] = 1
Столбец готов к анализу.
Столбец seats.¶
description(data.query('seats != -1'), 'seats', bins=100)
'Основные характеристики столбца'
count 4792.000000 mean 108.361436 std 122.841130 min 0.000000 25% 40.000000 50% 75.000000 75% 140.000000 max 1288.000000 Name: seats, dtype: float64
В столбце присутствуют нулевые значения. Изучим эти строки.
data[(data['seats'] == 0)]
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 84 | Meat Doner Kebab | булочная | Москва, улица Лескова, 22 | Северо-Восточный административный округ | ежедневно, круглосуточно | 55.896987 | 37.608126 | 4.5 | неизвестно | Средний счёт:300 ₽ | 300 | -1 | 0 | 0 |
| 177 | Арамье | булочная | Москва, улица 800-летия Москвы, 22, корп. 2 | Северный административный округ | ежедневно, 09:00–21:00 | 55.879392 | 37.556832 | 4.0 | неизвестно | неизвестно | -1 | -1 | 1 | 0 |
| 196 | Донер-Шашлык | ресторан | Москва, улица Лескова, 22 | Северо-Восточный административный округ | ежедневно, круглосуточно | 55.896962 | 37.608300 | 4.5 | неизвестно | неизвестно | -1 | -1 | 0 | 0 |
| 203 | Тандыр № 1 | кафе | Москва, улица Лескова, 22Г | Северо-Восточный административный округ | ежедневно, круглосуточно | 55.895615 | 37.611049 | 4.0 | неизвестно | неизвестно | -1 | -1 | 1 | 0 |
| 211 | Неаполитан пицца | кафе | Москва, улица Пришвина, 23 | Северо-Восточный административный округ | ежедневно, 12:00–23:00 | 55.885416 | 37.604650 | 4.3 | неизвестно | неизвестно | -1 | -1 | 0 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 8118 | Бико | булочная | Москва, улица Симоновский Вал, 13, стр. 3 | Юго-Восточный административный округ | ежедневно, 08:00–20:00 | 55.723340 | 37.664252 | 1.3 | неизвестно | неизвестно | -1 | -1 | 0 | 0 |
| 8168 | Чайхана УЧ Кудук | кофейня | Москва, улица Симоновский Вал, 26, корп. 1 | Южный административный округ | ежедневно, 09:00–00:00 | 55.718663 | 37.662887 | 4.3 | неизвестно | неизвестно | -1 | -1 | 0 | 0 |
| 8172 | Масса кофе | кофейня | Москва, Холодильный переулок, 2 | Южный административный округ | пн-пт 08:00–20:00; сб,вс 09:00–20:00 | 55.709713 | 37.624388 | 4.3 | средние | Цена чашки капучино:100–210 ₽ | -1 | 155 | 0 | 0 |
| 8336 | Сочная шаурма в Кузьминках | быстрое питание | Москва, Волгоградский проспект, 102 | Юго-Восточный административный округ | ежедневно, круглосуточно | 55.703834 | 37.773831 | 3.9 | низкие | Средний счёт:120–130 ₽ | 125 | -1 | 0 | 0 |
| 8362 | Достор | кафе | Москва, Вешняковская улица, 43 | Восточный административный округ | ежедневно, 09:00–00:00 | 55.716153 | 37.821948 | 4.1 | средние | Средний счёт:300–800 ₽ | 550 | -1 | 0 | 0 |
136 rows × 14 columns
По-видимому, речь идёт о заведениях, работающих строго на вынос, и ошибки здесь нет.
В Москве на сегодняшний день есть банкетные залы с вместимостью более 1000 человек, поэтому явных ошибок здесь нет.
Тем не менее, такие данные могут содержать ошибки и аномалии, поэтому для анализа данного столбца лучше оспользовать медиану, а не среднее.
Добавление столбцов¶
Cтолбец street с названиями улиц из столбца с адресом.¶
data.insert(
data.columns.get_loc('address') + 1,
'street',
data['address'].str.split(',').apply(lambda x: x[1])
)
Столбец is_24_7 с обозначением, что заведение работает ежедневно и круглосуточно (24/7).¶
data.insert(
data.columns.get_loc('hours') + 1,
'is_24_7',
data['hours'].apply(lambda x: True if (
('ежедневно' in x) & ('круглосуточно' in x)
) else False)
)
Датафрейм с новыми столбцами.
data.head(20)
| name | category | address | street | district | hours | is_24_7 | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | улица Дыбенко | Северный административный округ | ежедневно, 10:00–22:00 | False | 55.878494 | 37.478860 | 5.0 | неизвестно | неизвестно | -1 | -1 | 0 | -1 |
| 1 | Четыре комнаты | ресторан | Москва, улица Дыбенко, 36, корп. 1 | улица Дыбенко | Северный административный округ | ежедневно, 10:00–22:00 | False | 55.875801 | 37.484479 | 4.5 | выше среднего | Средний счёт:1500–1600 ₽ | 1550 | -1 | 0 | 4 |
| 2 | Хазри | кафе | Москва, Клязьминская улица, 15 | Клязьминская улица | Северный административный округ | пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00–02:00 | False | 55.889146 | 37.525901 | 4.6 | средние | Средний счёт:от 1000 ₽ | 1000 | -1 | 0 | 45 |
| 3 | Dormouse Coffee Shop | кофейня | Москва, улица Маршала Федоренко, 12 | улица Маршала Федоренко | Северный административный округ | ежедневно, 09:00–22:00 | False | 55.881608 | 37.488860 | 5.0 | неизвестно | Цена чашки капучино:155–185 ₽ | -1 | 170 | 0 | -1 |
| 4 | Иль Марко | пиццерия | Москва, Правобережная улица, 1Б | Правобережная улица | Северный административный округ | ежедневно, 10:00–22:00 | False | 55.881166 | 37.449357 | 5.0 | средние | Средний счёт:400–600 ₽ | 500 | -1 | 1 | 148 |
| 5 | Sergio Pizza | пиццерия | Москва, Ижорская улица, вл8Б | Ижорская улица | Северный административный округ | ежедневно, 10:00–23:00 | False | 55.888010 | 37.509573 | 4.6 | средние | неизвестно | -1 | -1 | 0 | -1 |
| 6 | Огни города | бар,паб | Москва, Клязьминская улица, 9, стр. 3 | Клязьминская улица | Северный административный округ | пн 15:00–04:00; вт-вс 15:00–05:00 | False | 55.890752 | 37.524653 | 4.4 | средние | Средний счёт:199 ₽ | 199 | -1 | 0 | 45 |
| 7 | Mr. Уголек | быстрое питание | Москва, Клязьминская улица, 9, стр. 3 | Клязьминская улица | Северный административный округ | пн-чт 10:00–22:00; пт,сб 10:00–23:00; вс 10:00–22:00 | False | 55.890636 | 37.524303 | 4.7 | средние | Средний счёт:200–300 ₽ | 250 | -1 | 0 | 45 |
| 8 | Donna Maria | ресторан | Москва, Дмитровское шоссе, 107, корп. 4 | Дмитровское шоссе | Северный административный округ | ежедневно, 10:00–22:00 | False | 55.880045 | 37.539006 | 4.8 | средние | Средний счёт:от 500 ₽ | 500 | -1 | 0 | 79 |
| 9 | Готика | кафе | Москва, Ангарская улица, 39 | Ангарская улица | Северный административный округ | ежедневно, 12:00–00:00 | False | 55.879038 | 37.524487 | 4.3 | средние | Средний счёт:1000–1200 ₽ | 1100 | -1 | 0 | 65 |
| 10 | Great Room Bar | бар,паб | Москва, Левобережная улица, 12 | Левобережная улица | Северный административный округ | ежедневно, круглосуточно | True | 55.877832 | 37.469171 | 4.5 | средние | Цена бокала пива:250–350 ₽ | -1 | -1 | 0 | 102 |
| 11 | Шашлык Шефф | кафе | Москва, улица Маршала Федоренко, 10с1 | улица Маршала Федоренко | Северный административный округ | ежедневно, 10:00–21:00 | False | 55.881770 | 37.492362 | 4.9 | неизвестно | неизвестно | -1 | -1 | 0 | -1 |
| 12 | Заправка | кафе | Москва, МКАД, 80-й километр, 1 | МКАД | Северный административный округ | вт-сб 09:00–18:00 | False | 55.899938 | 37.517958 | 4.3 | средние | Средний счёт:330 ₽ | 330 | -1 | 0 | -1 |
| 13 | Буханка | булочная | Москва, Базовская улица, 15, корп. 1 | Базовская улица | Северный административный округ | ежедневно, 08:00–22:00 | False | 55.877007 | 37.504980 | 4.8 | неизвестно | неизвестно | -1 | -1 | 1 | 180 |
| 14 | У Сильвы | бар,паб | Москва, Ангарская улица, 42с1 | Ангарская улица | Северный административный округ | ежедневно, 13:00–00:00 | False | 55.885528 | 37.528371 | 4.2 | выше среднего | Средний счёт:1500 ₽ | 1500 | -1 | 0 | -1 |
| 15 | Дом обеда | столовая | Москва, улица Бусиновская Горка, 2 | улица Бусиновская Горка | Северный административный округ | пн-пт 08:30–18:30; сб 10:00–20:00 | False | 55.885890 | 37.493264 | 4.1 | средние | Средний счёт:300–500 ₽ | 400 | -1 | 0 | 180 |
| 16 | База Стритфуд | кафе | Москва, Базовская улица, 15, корп. 8 | Базовская улица | Северный административный округ | ежедневно, 10:00–23:00 | False | 55.877859 | 37.507754 | 4.2 | средние | Средний счёт:140–350 ₽ | 245 | -1 | 0 | -1 |
| 17 | Чайхана Беш-Бармак | ресторан | Москва, Ленинградское шоссе, 71Б, стр. 2 | Ленинградское шоссе | Северный административный округ | ежедневно, круглосуточно | True | 55.876908 | 37.449876 | 4.4 | средние | Средний счёт:350–500 ₽ | 425 | -1 | 0 | 96 |
| 18 | Час-Пик | столовая | Москва, Коровинское шоссе, 30А | Коровинское шоссе | Северный административный округ | ежедневно, 09:00–21:00 | False | 55.884651 | 37.517482 | 4.3 | средние | Средний счёт:200–300 ₽ | 250 | -1 | 0 | 25 |
| 19 | Пекарня | булочная | Москва, Ижорский проезд, 5 | Ижорский проезд | Северный административный округ | ежедневно, круглосуточно | True | 55.887969 | 37.515688 | 4.4 | неизвестно | неизвестно | -1 | -1 | 1 | -1 |
Вывод по разделу предобработки¶
Полных дубликатов строк в датасете нет, неявные дубликаты названий заведений исправлены.
В датасете довольно много пропусков в столбцах hours, price, avg_bill, middle_avg_bill, middle_coffee_cup, seats. Удалось частично заполнить пропуски в столбце price. На пропуски в остальных столбцах установлены значения-заглушки.
Значения столбцов seats, middle_avg_bill и middle_coffee_cup приведены к типу int.
Данные проверены на очевидные ошибки и аномалии:
- в столбцах middle_avg_bill и middle_coffee_cup удалены нулевые значения, выбросы и явные ошибки;
- в столбце chain исправлены явные ошибки.
Добавлены столбцы:
- столбец street с названиями улиц из столбца с адресом;
- столбец is_24_7 с обозначением, что заведение работает ежедневно и круглосуточно (24/7):
- True — если заведение работает ежедневно и круглосуточно;
- False — в противоположном случае.
Исследовательский анализ данных¶
Количество объектов общественного питания по категориям.¶
cat_visualise(
data,
'category',
'Категория',
'name',
'count',
'Количество'
)
Больше всего в Москве кафе (2376, 28,3%), ресторанов (2042, 24,3%) и кофеен (1413, 16,8%), меньше всего - булочных (256, 3%) и столовых (315, 3,7%).
Количество посадочных мест в заведениях по категориям.¶
Соотношение суммарного количества посадочных мест объектов по категориям.
#удаление пропусков
data_filtered = data.query('seats != -1')
cat_visualise(
data_filtered,
'category',
'Категория',
'seats',
'sum',
'Суммарное количество посадочных мест'
)
Наибольшее суммарное количество посадочных мест в ресторанах (154681, 29,8%), кафе (118494, 22,8%) и кофейнях (83511, 16,1%), меньше всего - в булочных (13229, 2,5%) и столовых (13229, 3,2%).
cat_visualise(
data_filtered,
'category',
'Категория',
'seats',
'median',
'Медианное количество посадочных мест'
)
Наибольшее медианное количество посадочных мест в ресторанах (86, 15,5%), барах (82, 14,8%) и кофейнях (80, 14,5%) , меньше всего - в булочных (50, 9%).
Медианное количество посадочных мест в заведениях по категориям распределено более равномерно, чем суммарное: показатель лидера отличается от наименьшего показателя всего в 1,72 раза.
Cоотношение сетевых и несетевых заведений.¶
data_chain = data.copy()
data_chain['chain'] = data_chain['chain'].replace({
1:'сетевые',
0:'несетевые'
})
cat_visualise(
data_chain,
'chain',
'Сетевые и несетевые',
'name',
'count',
'Количество'
)
Сетевых заведений меньше, чем несетевых: 3242 (38,6%) и 5160 (61,4%) соответственно.
Распределение сетевых заведений по категориям.¶
#сводная таблица
pivot = data.pivot_table(
index='category',
columns='chain',
values='name',
aggfunc='count'
)
pivot['total'] = pivot[[0, 1]].sum(axis=1)
pivot.columns = ['nonchain', 'chain', 'total']
pivot = pivot.sort_values(by='total')
#диаграмма количества объектов общественного питания по категориям
ax = pivot[['nonchain', 'chain', 'total']].plot(
kind='barh',
figsize=(15,10),
width=0.8
)
ax.bar_label(ax.containers[0], fontsize=12)
ax.bar_label(ax.containers[1], fontsize=12)
ax.bar_label(ax.containers[2], fontsize=12)
ax.set_title(f'Соотношение количества сетевых и несетевых заведений, разбивка по категориям.png')
ax.set_xlabel('Количество')
ax.set_ylabel('Категория');
ax.legend(['Несетевые','Сетевые', 'Общее количество'])
#сохранение изображения
plt_name = pics_path + 'Соотношение количества сетевых и несетевых заведений, разбивка по категориям.png'
plt.savefig(plt_name)
plt.show();
#столбец с процентами сетевых заведений ко всем
pivot['chain_percent'] = pivot['chain'] / pivot['total'] * 100
#диаграмма долей объектов общественного питания по категориям
plt.figure(figsize=(8,8))
ax = plt.pie(
pivot['chain_percent'],
labels=pivot.index,
autopct='%0.1f%%'
)
plt.title('Процент сетевых заведений к общему количеству. Соотношение по категориям')
#сохранение изображения
plt_name = pics_path + 'Процент сетевых заведений к общему количеству. Соотношение по категориям.png'
plt.savefig(plt_name)
plt.show();
В абсолютном выражении среди сетевых заведений наибольшее количество кафе (792 из 2376), ресторанов (742 из 2042) и кофеен (726 из 1413), меньше всего - столовых (89 из 315) и булочных (157 из 256).
Однако наиболее частые по доле сетевых заведений относительно общего количества - булочные (18,9%), пиццерии (16,1%) и кофейни (15,8%), наименее частые: столовые (8,7%) и бары (6,8%).
Из этого можно сделать вывод, что наиболее часто сетевые модели построения бизнеса используются для кофеен, наименее часто - для столовых.
Топ-15 популярных сетей в Москве по количеству заведений.¶
#группировка данных
top_chains =(
data[data['chain'] == 1]
.groupby('name', as_index=False)[['chain','category']]
.agg({'chain':'count', 'category':'max'})
.sort_values(by='chain', ascending=False)
.head(15)
)
top_chains.columns = ['name','amount', 'category']
data_top_15 = data[data['name'].isin(top_chains['name'])]
display(
top_chains,
data_top_15.shape
)
| name | amount | category | |
|---|---|---|---|
| 730 | Шоколадница | 120 | кофейня |
| 337 | Домино'с Пицца | 77 | пиццерия |
| 333 | Додо Пицца | 74 | пиццерия |
| 146 | One Price Coffee | 72 | кофейня |
| 743 | Яндекс Лавка | 69 | ресторан |
| 58 | Cofix | 65 | кофейня |
| 168 | Prime | 50 | ресторан |
| 665 | Хинкальная | 44 | столовая |
| 369 | КОФЕПОРТ | 42 | кофейня |
| 420 | Кулинарная лавка братьев Караваевых | 39 | кафе |
| 629 | Теремок | 38 | ресторан |
| 684 | Чайхана | 37 | ресторан |
| 270 | Буханка | 32 | кофейня |
| 39 | CofeFest | 32 | кофейня |
| 478 | Му-Му | 27 | столовая |
(818, 16)
Топ-15 сетевых заведений составляют 818 строк, это 9,7% датасета.
#топ-15 заведений в разбивке по названиям
cat_visualise(
data_top_15,
'name',
'Название сети',
'chain',
'count',
'Количество (топ-15)'
)
Среди 15 самых популярных сетевых заведений с большим отрывом лидирует сеть кофеен "Шоколадница" (120 заведений, 14,7%). Наименее распространена сеть столовых "Му-Му" (27 заведений, 3,3%)
#топ-15 заведений в разбивке по категориям
cat_visualise(
data_top_15,
'category',
'Категория',
'chain',
'count',
'Количество (топ-15) сетевых'
)
Из топ-15 наибольшее количество составляют кофейни - 41,2%. Меньше всего столовые - 0,2%.
Сравнение по категориальным признакам.
#сравнение по категориальным признакам
for cat_col in ['category', 'district','is_24_7']:
display(
f'Сравнение по признаку {cat_col}',
compare_cat(data, data_top_15, cat_col, 10)
)
'Сравнение по признаку category'
| category | count_full | rate_full | count_filtered | rate_filt | deviation_abs | deviation_percent | |
|---|---|---|---|---|---|---|---|
| 0 | бар,паб | 764 | 0.090931 | 4 | 0.004890 | 0.086041 | -94.6 |
| 2 | быстрое питание | 603 | 0.071769 | 12 | 0.014670 | 0.057099 | -79.6 |
| 3 | кафе | 2376 | 0.282790 | 100 | 0.122249 | 0.160540 | -56.8 |
| 4 | кофейня | 1413 | 0.168174 | 337 | 0.411980 | -0.243806 | 145.0 |
| 5 | пиццерия | 633 | 0.075339 | 152 | 0.185819 | -0.110480 | 146.6 |
| 7 | столовая | 315 | 0.037491 | 2 | 0.002445 | 0.035046 | -93.5 |
'Сравнение по признаку district'
| district | count_full | rate_full | count_filtered | rate_filt | deviation_abs | deviation_percent | |
|---|---|---|---|---|---|---|---|
| 4 | Северо-Западный административный округ | 409 | 0.048679 | 46 | 0.056235 | -0.007556 | 15.5 |
| 6 | Юго-Восточный административный округ | 714 | 0.084980 | 56 | 0.068460 | 0.016520 | -19.4 |
'Сравнение по признаку is_24_7'
| is_24_7 | count_full | rate_full | count_filtered | rate_filt | deviation_abs | deviation_percent | |
|---|---|---|---|---|---|---|---|
| 1 | True | 730 | 0.086884 | 42 | 0.051345 | 0.035539 | -40.9 |
#сравнение по числовым признакам
for cat_col in [
'rating',
'middle_avg_bill',
'middle_coffee_cup',
'seats'
]:
result = compare_numeric(data, data_top_15, cat_col)
display(
f'Сравнение по показателю {cat_col}',
f'Коэффициент корелляции: {result[0]}',
f'Отклонение среднего значения: {result[1]}',
f'Среднее в основном датафрейме: {result[2]}',
f'Среднее в отфильтрованном датафрейме: {result[3]}',
f''
)
'Сравнение по показателю rating'
'Коэффициент корелляции: 1.0'
'Отклонение среднего значения: -0.0201'
'Среднее в основном датафрейме: 4.23'
'Среднее в отфильтрованном датафрейме: 4.1451'
''
'Сравнение по показателю middle_avg_bill'
'Коэффициент корелляции: 1.0'
'Отклонение среднего значения: -0.4991'
'Среднее в основном датафрейме: 941.4661'
'Среднее в отфильтрованном датафрейме: 471.5573'
''
'Сравнение по показателю middle_coffee_cup'
'Коэффициент корелляции: 1.0'
'Отклонение среднего значения: -0.068'
'Среднее в основном датафрейме: 172.1124'
'Среднее в отфильтрованном датафрейме: 160.4161'
''
'Сравнение по показателю seats'
'Коэффициент корелляции: 1.0'
'Отклонение среднего значения: 0.0101'
'Среднее в основном датафрейме: 108.3614'
'Среднее в отфильтрованном датафрейме: 109.4539'
''
Корелляция всех числовых признаков близка к 1, различие есть в среднем значении среднего счёта (на 50%). Построим для этого показателя график совместного распределения.
#график совметного распределения
ax = sns.jointplot(
x=data['middle_avg_bill'][data['middle_avg_bill'] != -1],
y=data_top_15['middle_avg_bill'][data_top_15['middle_avg_bill'] != -1],
kind='reg');
ax.fig.suptitle('График совместного распределения среднего счёта')
ax.set_axis_labels('Основной датафрейм', 'Отфильтрованный датафрейм')
ax.fig.tight_layout();
plt.show();
Из топ-15 наибольшее количество составляют кофейни - 41,2%. Меньше всего столовые - 0,2%.
Топ-15 сетевых заведений сильно выделяются соотношением по категориям: среди них больше доли кофеен (доля больше на 145%) и пиццерий (доля больше на 146,6%), меньше доли заведений быстрого питания (доля меньше на 79,6%) и кафе (доля меньше на 56,8%), почти не представлены бары и столовые.
Подавляющее большинство сетевых заведений не работают круглосуточно вне зависимости от их категории - доля круглосуточных сетевых заведений на 40,9 % меньше, чем всех.
Также доля сетевых заведений несколько меньше в Северо-Западном и Юго-восточном административном округах.
Средний счёт сетевых заведений сосредоточен около значения 471,6, в то время как средний счёт всех заведений распределён более равномерно по большему диапазону значений.
Выяснить, какие административные районы Москвы присутствуют в датасете. Отобразить общее количество заведений и количество заведений каждой категории по районам.¶
#сводная таблица
pivot = data.groupby(['district', 'category'])['name'].agg('count')
pivot = pivot.reset_index()
pivot.columns= ['district', 'Категория', 'Количество']
#таблица общего количества по категориям
total = pivot.groupby('district')['Количество'].agg('sum')
#построение графика
fig = px.bar(
pivot,
x='district',
y='Количество',
color='Категория',
text='Количество',
width=1000,
height=1500,
title='Распределение заведений и их категорий по 15 улицам с наибольшим количеством заведений',
)
fig.update_layout(
xaxis_title='Округа'
)
for dist in total.index:
fig.add_annotation(
x=dist,
y=total[dist],
text=str(total[dist])
)
fig.show()
Наибольшее количество заведений - в Центральном административном округе.
Распределение средних рейтингов по категориям заведений.¶
#диаграмма среднего рейтинга
plt.figure(figsize=(15,10))
ax = sns.barplot(
data=data,
x='category',
y='rating',
errorbar=None,
estimator='mean',
width=0.6,
)
ax.bar_label(ax.containers[0], fontsize=12, fmt='%0.2f')
ax.set_ylim(3.5,4.5)
ax.set_title('Распределение средних рейтингов по категориям')
ax.set_xlabel('Категория')
ax.set_ylabel('Средний рейтинг')
plt.savefig(
pics_path + 'Распределение средних рейтингов по категориям'
)
plt.show;
#диаграмма среднего рейтинга
plt.figure(figsize=(15,10))
ax = sns.boxplot(
data=data,
x='category',
y='rating',
width=0.4,
palette='muted'
)
ax.set_title('Распределение средних рейтингов по категориям - диаграммы размаха')
ax.set_xlabel('Категория')
ax.set_ylabel('Средний рейтинг')
plt.savefig(
pics_path + 'Распределение средних рейтингов по категориям - диаграммы размаха'
)
plt.show;
#график распределения рейтингов по категориям
plt.figure(figsize=(15,10))
ax = sns.kdeplot(
data.rename(columns={'category':'Категория'}),
x='rating',
hue='Категория')
ax.set_title('Распределение рейтингов по категориям')
ax.set_xlabel('Рейтинг')
ax.set_ylabel('Частота')
plt.savefig(
pics_path + 'Распределение рейтингов по категориям'
)
plt.show();
Средние рейтинги заведений по категориям различаются слабо: от 4,05 (быстрое питание) до 4,39 (бар,паб).
Фоновая картограмма (хороплет) со средним рейтингом заведений каждого района.¶
#координаты Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
#границы округов
districts_geo = 'admin_level_geomap.geojson'
#медианный рейтинг заведений по районам
rating = (
data
.groupby('district', as_index=False)['rating']
.agg('mean')
)
#создание карты
m = folium.Map(
location=[moscow_lat, moscow_lng],
zoom_start=10,
tiles='Cartodb Positron')
#фоновая картограмма
Choropleth(
geo_data=districts_geo,
data=rating,
columns=['district','rating'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.5,
legend_name='Средний рейтинг заведений по районам',
).add_to(m)
#вывод
m
rating
| district | rating | |
|---|---|---|
| 0 | Восточный административный округ | 4.174185 |
| 1 | Западный административный округ | 4.181647 |
| 2 | Северный административный округ | 4.240980 |
| 3 | Северо-Восточный административный округ | 4.147978 |
| 4 | Северо-Западный административный округ | 4.208802 |
| 5 | Центральный административный округ | 4.377520 |
| 6 | Юго-Восточный административный округ | 4.101120 |
| 7 | Юго-Западный административный округ | 4.172920 |
| 8 | Южный административный округ | 4.184417 |
Выше всего средние рейтинги в Центральном (4,38), Северном (4,24) и Северо-Западном (4,21) административных округах. Наименьший средний рейтинг в Юго-Восточном административном округе (4,1).
Отображение всех заведений датасета на карте с помощью кластеров средствами библиотеки folium.¶
#добавление пустого кластера
marker_cluster = MarkerCluster().add_to(m)
#функция для создания маркеров
def create_clusters(row):
Marker(
[row['lat'], row['lng']],
popup=f"{row['name']} {row['rating']}",
).add_to(marker_cluster)
# применяем функцию create_clusters() к каждой строке датафрейма
data.apply(create_clusters, axis=1)
#вывод
m
Топ-15 улиц по количеству заведений. График распределения количества заведений и их категорий по этим улицам.¶
#сводная таблица
top_streets = (
data
.groupby(['street','category'], as_index=False)['name']
.agg('count')
)
top_streets.columns = ['street', 'Категория', 'Количество']
#названия топ-15 улиц
street_names = (
top_streets
.groupby('street')['Количество'].sum()
.sort_values()
.tail(15)
.index
)
#фильтрация сводной таблицы по названиям топ-15 улиц
top_streets = (
top_streets[top_streets['street']
.isin(street_names)]
)
#построение графика
fig = px.bar(
top_streets,
x='street',
y='Количество',
color='Категория',
text='Количество',
width=1000,
height=1500,
title='Распределение заведений и их категорий по 15 улицам с наибольшим количеством заведений',
)
fig.update_layout(
xaxis_title='Улицы'
)
fig.show()
Больше всего заведений на проспекте Мира (183), меньше всего - на Пятницкой улице (48) и улице Миклухо-Маклая (49).
Улицы, на которых находится только один объект общепита.¶
#список улиц
lonely_streets = (
data
.groupby('street')['name']
.agg('count')
)
lonely_streets = lonely_streets[lonely_streets == 1].index
#фильтрация фатафрейма по списку улиц
data_lonely = data[data['street'].isin(lonely_streets)]
data_lonely.head(10)
| name | category | address | street | district | hours | is_24_7 | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 15 | Дом обеда | столовая | Москва, улица Бусиновская Горка, 2 | улица Бусиновская Горка | Северный административный округ | пн-пт 08:30–18:30; сб 10:00–20:00 | False | 55.885890 | 37.493264 | 4.1 | средние | Средний счёт:300–500 ₽ | 400 | -1 | 0 | 180 |
| 21 | 7/12 | кафе | Москва, Прибрежный проезд, 7 | Прибрежный проезд | Северный административный округ | ежедневно, 10:00–22:00 | False | 55.876805 | 37.464934 | 4.5 | неизвестно | неизвестно | -1 | -1 | 0 | -1 |
| 25 | В парке вкуснее | кофейня | Москва, парк Левобережный | парк Левобережный | Северный административный округ | ежедневно, 10:00–21:00 | False | 55.878453 | 37.460028 | 4.3 | неизвестно | неизвестно | -1 | -1 | 1 | -1 |
| 58 | Coffeekaldi's | кофейня | Москва, Угличская улица, 13, стр. 8 | Угличская улица | Северо-Восточный административный округ | ежедневно, 09:00–22:00 | False | 55.900316 | 37.570558 | 4.1 | средние | Средний счёт:500–800 ₽ | 650 | -1 | 1 | -1 |
| 60 | Чебуречная история | кофейня | Москва, ландшафтный заказник Лианозовский | ландшафтный заказник Лианозовский | Северо-Восточный административный округ | ежедневно, 10:00–22:00 | False | 55.899845 | 37.570488 | 4.9 | неизвестно | неизвестно | -1 | -1 | 1 | -1 |
| 64 | Testo мания | кофейня | Москва, Лианозовский парк культуры и отдыха | Лианозовский парк культуры и отдыха | Северо-Восточный административный округ | ежедневно, 09:00–21:00 | False | 55.900058 | 37.570544 | 4.1 | неизвестно | неизвестно | -1 | -1 | 0 | -1 |
| 71 | CofeFest | кофейня | Москва, Новгородская улица, 23А | Новгородская улица | Северо-Восточный административный округ | пн-пт 08:00–19:00 | False | 55.901799 | 37.577672 | 4.1 | неизвестно | неизвестно | -1 | -1 | 1 | -1 |
| 73 | Веранда | ресторан | Москва, парк Алтуфьево | парк Алтуфьево | Северо-Восточный административный округ | пн-пт 11:00–22:00; сб,вс 11:00–23:00 | False | 55.906875 | 37.582493 | 4.2 | неизвестно | неизвестно | -1 | -1 | 1 | -1 |
| 97 | Жигулевское | бар,паб | Москва, Бибиревская улица, 7к2 | Бибиревская улица | Северо-Восточный административный округ | пн-чт 14:00–00:00; пт,сб 14:00–02:00; вс 14:00–00:00 | False | 55.879733 | 37.593734 | 4.5 | средние | Цена бокала пива:90–230 ₽ | -1 | -1 | 0 | -1 |
| 103 | Тетри | быстрое питание | Москва, Лианозовский проезд, вл1 | Лианозовский проезд | Северо-Восточный административный округ | вт-вс 09:00–19:00 | False | 55.896922 | 37.554929 | 4.0 | средние | Средний счёт:150–250 ₽ | 200 | -1 | 0 | -1 |
Распределение по категориям.
cat_visualise(
data_lonely,
'category',
'Категория',
'name',
'count',
'Количество (улицы только с одним объектом)'
)
Сравнение по категориальным признакам.
#сравнение по категориальным признакам
for cat_col in [
'category',
'district',
'is_24_7',
'chain']:
display(
f'Сравнение по признаку {cat_col}',
compare_cat(data, data_lonely, cat_col, 20)
)
'Сравнение по признаку category'
| category | count_full | rate_full | count_filtered | rate_filt | deviation_abs | deviation_percent | |
|---|---|---|---|---|---|---|---|
| 1 | булочная | 256 | 0.030469 | 8 | 0.017429 | 0.013040 | -42.8 |
| 2 | быстрое питание | 603 | 0.071769 | 23 | 0.050109 | 0.021660 | -30.2 |
| 3 | кафе | 2376 | 0.282790 | 160 | 0.348584 | -0.065794 | 23.3 |
| 5 | пиццерия | 633 | 0.075339 | 15 | 0.032680 | 0.042659 | -56.6 |
| 7 | столовая | 315 | 0.037491 | 36 | 0.078431 | -0.040940 | 109.2 |
'Сравнение по признаку district'
| district | count_full | rate_full | count_filtered | rate_filt | deviation_abs | deviation_percent | |
|---|---|---|---|---|---|---|---|
| 1 | Западный административный округ | 850 | 0.101166 | 35 | 0.076253 | 0.024914 | -24.6 |
| 7 | Юго-Западный административный округ | 709 | 0.084385 | 18 | 0.039216 | 0.045169 | -53.5 |
'Сравнение по признаку is_24_7'
| is_24_7 | count_full | rate_full | count_filtered | rate_filt | deviation_abs | deviation_percent | |
|---|---|---|---|---|---|---|---|
| 1 | True | 730 | 0.086884 | 31 | 0.067538 | 0.019346 | -22.3 |
'Сравнение по признаку chain'
| chain | count_full | rate_full | count_filtered | rate_filt | deviation_abs | deviation_percent | |
|---|---|---|---|---|---|---|---|
| 1 | 1 | 3242 | 0.385861 | 134 | 0.291939 | 0.093922 | -24.3 |
#сравнение по числовым признакам
for cat_col in [
'rating',
'middle_avg_bill',
'middle_coffee_cup',
'seats'
]:
result = compare_numeric(data, data_lonely, cat_col)
display(
f'Сравнение по показателю {cat_col}',
f'Коэффициент корелляции: {result[0]}',
f'Отклонение среднего значения: {result[1]}',
f'Среднее в основном датафрейме: {result[2]}',
f'Среднее в отфильтрованном датафрейме: {result[3]}',
f''
)
'Сравнение по показателю rating'
'Коэффициент корелляции: 1.0'
'Отклонение среднего значения: 0.0015'
'Среднее в основном датафрейме: 4.23'
'Среднее в отфильтрованном датафрейме: 4.2364'
''
'Сравнение по показателю middle_avg_bill'
'Коэффициент корелляции: 1.0'
'Отклонение среднего значения: 0.0195'
'Среднее в основном датафрейме: 941.4661'
'Среднее в отфильтрованном датафрейме: 959.8303'
''
'Сравнение по показателю middle_coffee_cup'
'Коэффициент корелляции: 1.0'
'Отклонение среднего значения: 0.0817'
'Среднее в основном датафрейме: 172.1124'
'Среднее в отфильтрованном датафрейме: 186.1667'
''
'Сравнение по показателю seats'
'Коэффициент корелляции: 1.0'
'Отклонение среднего значения: -0.4545'
'Среднее в основном датафрейме: 108.3614'
'Среднее в отфильтрованном датафрейме: 59.1154'
''
Корелляция всех числовых признаков близка к 1, различие есть в среднем значении количества посадочных мест (на 45,5%). Построим для этого показателя график совместного распределения.
#график совметного распределения
ax = sns.jointplot(
x=data['seats'][data['seats'] != -1],
y=data_top_15['seats'][data_top_15['seats'] != -1],
kind='reg');
ax.fig.suptitle(
'График совместного распределения количества посадочных мест'
)
ax.set_axis_labels(
'Основной датафрейм', 'Отфильтрованный датафрейм'
)
ax.fig.tight_layout();
plt.show();
Среди таких заведений больше всего кафе (160, 34,9%), ресторанов (94, 20,5%) и кофеен (84, 18,3%), однако их доля от общего количества значимо не отличается от доли всех заведений; меньше всего булочных (8, 1,7%), причём доля булочных на 42,8% меньше, чем в общей выборке.
Доли пиццерий и предприятий быстрого питания также существенно меньше: на 56,6% и 30,2% соответственно. Доля столовых, напритив, вдвое больше - на 109,2%.
Доли рассматриваемых заведений ниже при распределении п административным округам:
- в Западном административный округе на 24,6%;
- в Югоо-Западомй административомй окруе на -53.%.
Доля сетевых заведений меньше на 24,3%.
Есть отличие в среднем числе посадочных мест: 59,1 против 108,4 в основной выборке..
Фоновая картограмма (хороплет) с медианными значениями средних чеков для каждого района. Анализ цен в центральном административном округе и других.¶
#координаты Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
#границы округов
districts_geo = 'admin_level_geomap.geojson'
#медианный рейтинг заведений по районам
median_bills = (
data[data['middle_avg_bill'] != -1]
.groupby('district', as_index=False)['middle_avg_bill']
.agg('median')
)
#создание карты
m_bill = folium.Map(
location=[moscow_lat, moscow_lng],
zoom_start=10,
tiles='Cartodb Positron')
#фоновая картограмма
Choropleth(
geo_data=districts_geo,
data=median_bills,
columns=['district','middle_avg_bill'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.5,
legend_name='Медианный счёт в заведениях по районам',
).add_to(m_bill)
#вывод
m_bill
median_bills
| district | middle_avg_bill | |
|---|---|---|
| 0 | Восточный административный округ | 550.0 |
| 1 | Западный административный округ | 1000.0 |
| 2 | Северный административный округ | 650.0 |
| 3 | Северо-Восточный административный округ | 500.0 |
| 4 | Северо-Западный административный округ | 700.0 |
| 5 | Центральный административный округ | 1000.0 |
| 6 | Юго-Восточный административный округ | 450.0 |
| 7 | Юго-Западный административный округ | 600.0 |
| 8 | Южный административный округ | 500.0 |
Наибольший медианный средний счёт в Центральном и Западном административном округах - 1000 руб., наименьший - Северо-Восточном (500 руб.), Южном (500 руб.) и Юго-Восточном (450 руб).
Вывод по разделу исследовательского анализа данных¶
Количество заведений по категориям¶
Больше всего в Москве кафе (2376, 28,3%), ресторанов (2042, 24,3%) и кофеен (1413, 16,8%), меньше всего - булочных (256, 3%) и столовых (315, 3,7%).
Суммарное количество посадочных мест¶
Наибольшее суммарное количество посадочных мест в ресторанах (154681, 29,8%), кафе (118494, 22,8%) и кофейнях (83511, 16,1%), меньше всего - в булочных (13229, 2,5%) и столовых (13229, 3,2%).
Наибольшее медианное количество посадочных мест в ресторанах (86, 15,5%), барах (82, 14,8%) и кофейнях (80, 14,5%) , меньше всего - в булочных (50, 9%).
Медианное количество посадочных мест в заведениях по категориям распределено более равномерно, чем суммарное: показатель лидера отличается от наименьшего показателя всего в 1,72 раза.
Соотношение сетевых и несетевых заведений и их распределерие по категориям¶
Сетевых заведений меньше, чем несетевых: 3242 (38,6%) и 5160 (61,4%) соответственно.
В абсолютном выражении среди сетевых заведений наибольшее количество кафе (792 из 2376), ресторанов (742 из 2042) и кофеен (726 из 1413), меньше всего - столовых (89 из 315) и булочных (157 из 256).
Однако наиболее частые по доле сетевых заведений относительно общего количества - булочные (18,9%), пиццерии (16,1%) и кофейни (15,8%), наименее частые: столовые (8,7%) и бары (6,8%).
Из этого можно сделать вывод, что наиболее часто сетевые модели построения бизнеса используются для кофеен, наименее часто - для столовых.
Топ-15 сетевых заведений по количеству в сети¶
Среди 15 самых популярных сетевых заведений с большим отрывом лидирует сеть кофеен "Шоколадница" (120 заведений, 14,7%). Наименее распространена сеть столовых "Му-Му" (27 заведений, 3,3%)
Из топ-15 наибольшее количество составляют кофейни - 41,2%. Меньше всего столовые - 0,2%.
Топ-15 сетевых заведений сильно выделяются соотношением по категориям: среди них больше доли кофеен (доля больше на 145%) и пиццерий (доля больше на 146,6%), меньше доли заведений быстрого питания (доля меньше на 79,6%) и кафе (доля меньше на 56,8%), почти не представлены бары и столовые.
Подавляющее большинство сетевых заведений из топ-15 не работают круглосуточно вне зависимости от их категории - доля круглосуточных сетевых заведений на 40,9 % меньше, чем всех.
Также доля сетевых заведений из топ-15 несколько меньше в Северо-Западном и Юго-восточном административном округах.
Средний счёт сетевых заведений из топ-15 сосредоточен около значения 471,6, в то время как средний счёт всех заведений распределён более равномерно по большему диапазону значений со средним в 941,5.
Наибольшее количество заведений - в Центральном административном округе.¶
Распределение средних рейтингов заведений по категориям¶
Средние рейтинги заведений по категориям различаются слабо: от 4,05 (быстрое питание) до 4,39 (бар,паб).
Выше всего средние рейтинги в Центральном (4,38), Северном (4,24) и Северо-Западном (4,21) административных округах. Наименьший средний рейтинг в Юго-Восточном административном округе (4,1).
Распределение количества заведений по улицам¶
Больше всего заведений на проспекте Мира (183), меньше всего - на Пятницкой улице (48) и улице Миклухо-Маклая (49).
Улицы, на которых находится только одно заведение¶
Среди заведений, расположенных на улицах только с одним заведением больше всего кафе (160, 34,9%), ресторанов (94, 20,5%) и кофеен (84, 18,3%), однако их доля от общего количества значимо не отличается от доли всех заведений; меньше всего булочных (8, 1,7%), причём доля булочных на 42,8% меньше, чем в общей выборке.
Доли пиццерий и предприятий быстрого питания также существенно меньше: на 56,6% и 30,2% соответственно. Доля столовых, напритив, вдвое больше - на 109,2%.
Доли рассматриваемых заведений ниже при распределении п административным округам:
- в Западном административный округе на 24,6%;
- в Юго-Западном административном округе на 53.5%.
Доля сетевых заведений меньше на 24,3%.
Есть отличие в среднем числе посадочных мест: 59,1 против 108,4 в основной выборке.
Распределение медианного среднего счёта по округам¶
Наибольший медианный средний счёт в Центральном и Западном административном округах - 1000 руб., наименьший - Северо-Восточном (500 руб.), Южном (500 руб.) и Юго-Восточном (450 руб).
Исследование данных и рекомендации для открытия новой кофейни.¶
Создание датафрейма с кофейнями.
data_cshop = data[data['category'] == 'кофейня']
data
| name | category | address | street | district | hours | is_24_7 | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | улица Дыбенко | Северный административный округ | ежедневно, 10:00–22:00 | False | 55.878494 | 37.478860 | 5.0 | неизвестно | неизвестно | -1 | -1 | 0 | -1 |
| 1 | Четыре комнаты | ресторан | Москва, улица Дыбенко, 36, корп. 1 | улица Дыбенко | Северный административный округ | ежедневно, 10:00–22:00 | False | 55.875801 | 37.484479 | 4.5 | выше среднего | Средний счёт:1500–1600 ₽ | 1550 | -1 | 0 | 4 |
| 2 | Хазри | кафе | Москва, Клязьминская улица, 15 | Клязьминская улица | Северный административный округ | пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00–02:00 | False | 55.889146 | 37.525901 | 4.6 | средние | Средний счёт:от 1000 ₽ | 1000 | -1 | 0 | 45 |
| 3 | Dormouse Coffee Shop | кофейня | Москва, улица Маршала Федоренко, 12 | улица Маршала Федоренко | Северный административный округ | ежедневно, 09:00–22:00 | False | 55.881608 | 37.488860 | 5.0 | неизвестно | Цена чашки капучино:155–185 ₽ | -1 | 170 | 0 | -1 |
| 4 | Иль Марко | пиццерия | Москва, Правобережная улица, 1Б | Правобережная улица | Северный административный округ | ежедневно, 10:00–22:00 | False | 55.881166 | 37.449357 | 5.0 | средние | Средний счёт:400–600 ₽ | 500 | -1 | 1 | 148 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 8401 | Суши Мания | кафе | Москва, Профсоюзная улица, 56 | Профсоюзная улица | Юго-Западный административный округ | ежедневно, 09:00–02:00 | False | 55.670021 | 37.552480 | 4.4 | неизвестно | неизвестно | -1 | -1 | 0 | 86 |
| 8402 | Миславнес | кафе | Москва, Пролетарский проспект, 19, корп. 1 | Пролетарский проспект | Южный административный округ | ежедневно, 08:00–22:00 | False | 55.640875 | 37.656553 | 4.8 | неизвестно | неизвестно | -1 | -1 | 0 | 150 |
| 8403 | Самовар | кафе | Москва, Люблинская улица, 112А, стр. 1 | Люблинская улица | Юго-Восточный административный округ | ежедневно, круглосуточно | True | 55.648859 | 37.743219 | 3.9 | низкие | Средний счёт:от 150 ₽ | 150 | -1 | 0 | 150 |
| 8404 | Чайхана Sabr | кафе | Москва, Люблинская улица, 112А, стр. 1 | Люблинская улица | Юго-Восточный административный округ | ежедневно, круглосуточно | True | 55.648849 | 37.743222 | 4.2 | неизвестно | неизвестно | -1 | -1 | 1 | 150 |
| 8405 | Kebab Time | кафе | Москва, Россошанский проезд, 6 | Россошанский проезд | Южный административный округ | ежедневно, круглосуточно | True | 55.598229 | 37.604702 | 3.9 | неизвестно | неизвестно | -1 | -1 | 0 | 12 |
8402 rows × 16 columns
Визуализация в разбивке по основным признакам - категориальным и количественным.¶
#подготовка данных для визуализации
data_cshop['chain'] = data_cshop['chain'].replace({
1:'сетевые',
0:'несетевые'
})
data_cshop['is_24_7'] = data_cshop['is_24_7'].replace({
True:'24/7',
False:'обычный'
})
#словарь названий категориальных столбцов
dict={
'district':'Округ',
'is_24_7':'Режим работы',
'chain':'Сетевые или нет'}
#визуализация распределения по категориальным признакам
for cat in dict.keys():
cat_visualise(
data_cshop,
cat,
dict[cat],
'name',
'count',
'Кофейни - количество'
)
Больше всего кофеен в Центральном округе (428, 30,3%), меньше всего в Северо-Западном (62, 4,4%).
Кофеен, работающих в режиме "24/7" почти нет: всего 59 (4,2%).
Сетевых и несетевых кофеен примерно поровну: 726 (51,4%) и 687 (48,6%) соответственно.
Рейтинги кофеен¶
#основные характеристики столбца
description(
data_cshop,
'rating',
bins=30
)
'Основные характеристики столбца'
count 1413.000000 mean 4.277282 std 0.372250 min 1.400000 25% 4.100000 50% 4.300000 75% 4.400000 max 5.000000 Name: rating, dtype: float64
Средний рейтинг 4,28, медианный 4,3. Рейтинги распределены близко к среднему, но есть небольшое количество кофеен с низким рейтингом (3,6 и менее).
Распределение рейтингов по округам.
#диаграмма среднего рейтинга по округам
plt.figure(figsize=(15,7))
ax = sns.barplot(
data_cshop,
x='rating',
y='district',
errorbar=None,
estimator='mean',
width=0.6,
)
ax.bar_label(ax.containers[0], fontsize=12, fmt='%0.2f')
ax.set_xlim(3.5,4.5)
ax.set_title(
'Распределение средних рейтингов кофеен по округам'
)
ax.set_ylabel('Округ')
ax.set_xlabel('Средний рейтинг')
ax.tick_params(axis='x', rotation=75)
plt.savefig(
pics_path + 'Распределение средних рейтингов кофеен по округам.png',
bbox_inches= 'tight'
)
plt.show;
#диаграмма среднего рейтинга
plt.figure(figsize=(15,10))
ax = sns.boxplot(
data=data_cshop,
y='district',
x='rating',
width=0.6,
palette='muted'
)
ax.set_title('Распределение средних рейтингов по категориям - диаграммы размаха')
ax.set_ylabel('Категория')
ax.set_xlabel('Средний рейтинг')
plt.savefig(
pics_path + 'Распределение средних рейтингов по категориям - диаграммы размаха.png'
)
plt.show;
Самый высокий средний рейтинг у кофеен в Центральном административном округе (4,34), самый низкий - в западном (4,2).
Средняя стоимость чашки капучино¶
#удаление значений-заглушек
data_cshop_filtered = data_cshop[
data_cshop['middle_coffee_cup'] != -1
]
#основные характеристики столбца
description(
data_cshop_filtered,
'middle_coffee_cup',
bins=30
)
'Основные характеристики столбца'
count 520.000000 mean 172.376923 std 65.767854 min 60.000000 25% 124.000000 50% 169.500000 75% 225.000000 max 375.000000 Name: middle_coffee_cup, dtype: float64
Средняя цена чашки капучино 169,5 руб., медианная 172,4 руб. На гистограмме два явных пика: рядом со средним значением (169,5 руб.) и значением в 250 руб.
Распределение средней цены чашки капучино по округам.
#диаграмма среднего рейтинга по округам
plt.figure(figsize=(15,7))
ax = sns.barplot(
data_cshop_filtered,
x='middle_coffee_cup',
y='district',
errorbar=None,
estimator='mean',
width=0.6,
)
ax.bar_label(ax.containers[0], fontsize=12, fmt='%0.1f')
ax.set_xlim(100, 200)
ax.set_title(
'Распределение средней цены чашки капучино по округам'
)
ax.set_ylabel('Округ')
ax.set_xlabel('Cредняя цена чашки капучино')
plt.savefig(
pics_path + 'Распределение средней цены чашки капучино по округам.png',
bbox_inches= 'tight'
)
plt.show;
#диаграмма среднего рейтинга
plt.figure(figsize=(15,10))
ax = sns.boxplot(
data=data_cshop_filtered,
y='district',
x='middle_coffee_cup',
width=0.6,
palette='muted'
)
ax.set_title(
'Распределение средней цены чашки капучино по округам - диаграммы размаха'
)
ax.set_ylabel('Округ')
ax.set_xlabel('Cредняя цена чашки капучино')
plt.savefig(
pics_path + 'Распределение средней цены чашки капучино по округам - диаграммы размаха.png'
)
plt.show;
Наибольшая средняя цена чашки капучино в Западном (189,9 руб.), Центральном (187,5 руб.) и Юго-Западном административных округах; наименьшая - в Восточном административном округе (140).
При открытии новой кофейни лучше ориентироваться на среднюю чашку капучино по округу: цену больше ставить нежелательно, чтобы не отпугнуть посетителей, радикально меньшую цену ставить также не стоит, т.к. средняя цена по району, как правило, уже отражает соотношение себестоимости, расходов, прибыли и т.д. для данного района. В честь открытия и в период выхода на рынок для привлечения посетителей и формирования клиентской базы можно устанавливать цену ниже средней по району, а также устраивать акции, предлагать скидки и так далее.
Посадочные места в кофейнях - суммарное и медианное количество по округам¶
Количество посадочных мест.
#фильтрация пропусков, создание сводной таблицы, сортировка
pivot = (
data_cshop[data_cshop['seats'] != -1]
.groupby('district')['seats']
.agg('sum')
.sort_values(ascending=False)
).reset_index()
#диаграмма количества посадочных мест по округам
plt.figure(figsize=(15,7))
ax = sns.barplot(
data=pivot,
x='seats',
y='district',
width=0.6,
)
ax.bar_label(ax.containers[0], fontsize=12, fmt='%0.2f')
ax.set_title(
'Суммарное количество посадочных мест в кофейнях по округам'
)
ax.set_ylabel('Округ')
ax.set_xlabel('Суммарное количество посадочных мест ')
plt.savefig(
pics_path + 'Суммарное количество посадочных мест в кофейнях по округам.png',
bbox_inches= 'tight'
)
plt.show;
Медианное количество посадочных мест.
#фильтрация пропусков, создание сводной таблицы, сортировка
pivot = (
data_cshop[data_cshop['seats'] != -1]
.groupby('district')['seats']
.agg('median')
.sort_values(ascending=False)
).reset_index()
#диаграмма количества посадочных мест по округам
plt.figure(figsize=(15,7))
ax = sns.barplot(
data=pivot,
x='seats',
y='district',
width=0.6,
)
ax.bar_label(ax.containers[0], fontsize=12, fmt='%0.2f')
ax.set_title(
'Медианное количество посадочных мест в кофейнях по округам'
)
ax.set_ylabel('Округ')
ax.set_xlabel('Медианное количество посадочных мест ')
ax.tick_params(axis='x', rotation=75)
plt.savefig(
pics_path + 'Медианное количество посадочных мест в кофейнях по округам.png',
bbox_inches= 'tight'
)
plt.show;
Наибольшее суммарное количество посадочных мест в кофейнях - в Центральном административном округе (24666), медианное количество посадочных мест здесь 86.
Вывод по разделу и рекомендации для открытия нового заведения¶
Больше всего кофеен в Центральном округе (428, 30,3%), меньше всего в Северо-Западном (62, 4,4%).
Кофеен, работающих в режиме "24/7" почти нет: всего 59 (4,2%).
Сетевых и несетевых кофеен примерно поровну: 726 (51,4%) и 687 (48,6%) соответственно.
Средний рейтинг 4,28, медианный 4,3. Рейтинги распределены близко к среднему, но есть небольшое количество кофеен с низким рейтингом (3,6 и менее). Самый высокий средний рейтинг у кофеен в Центральном административном округе (4,38), самый низкий - в Юго-Восточном (4,1).
Средняя цена чашки капучино 169,5 руб. Наибольшая средняя цена чашки капучино в Западном (189,9 руб.), Центральном (187,5 руб.) и Юго-Западном административных округах; наименьшая - в Восточном административном округе (140).
Наибольшее суммарное количество посадочных мест в кофейнях - в Центральном административном округе (24666), медианное количество посадочных мест здесь 86.
Рекомендации.
Рекомендую основателям фонда «Shut Up and Take My Money» открыть новую кофейню в Центральном административном округе: здесь кофейни пользуются гораздо большим спросом и популярностью, чем в других округах. Из-за большого количества кофеен следует ожидать высокой конкуренции. При определении ценовой политики новой кофейни можно ориентироваться на среднюю цену чашки капучино в 187,5 руб. Количество посадочных мест в кофейне должно быть около 86.
Общий вывод¶
Цели и задачи проекта¶
Цель проекта: дать заказчикам рекомендацию для открытия нового заведения общественного питания в г.Москва.
Задачи проекта:
- Провести исследование рынка общественного питания Москвы
- Подготовить рекомендации заказчику для открытия нового заведения общественного питания в Москве
Исследовательский анализ данных¶
Количество заведений по категориям¶
Больше всего в Москве кафе (2376, 28,3%), ресторанов (2042, 24,3%) и кофеен (1413, 16,8%), меньше всего - булочных (256, 3%) и столовых (315, 3,7%).
Суммарное количество посадочных мест¶
Наибольшее суммарное количество посадочных мест в ресторанах (154681, 29,8%), кафе (118494, 22,8%) и кофейнях (83511, 16,1%), меньше всего - в булочных (13229, 2,5%) и столовых (13229, 3,2%).
Наибольшее медианное количество посадочных мест в ресторанах (86, 15,5%), барах (82, 14,8%) и кофейнях (80, 14,5%) , меньше всего - в булочных (50, 9%).
Медианное количество посадочных мест в заведениях по категориям распределено более равномерно, чем суммарное: показатель лидера отличается от наименьшего показателя всего в 1,72 раза.
Соотношение сетевых и несетевых заведений и их распределерие по категориям¶
Сетевых заведений меньше, чем несетевых: 3242 (38,6%) и 5160 (61,4%) соответственно.
В абсолютном выражении среди сетевых заведений наибольшее количество кафе (792 из 2376), ресторанов (742 из 2042) и кофеен (726 из 1413), меньше всего - столовых (89 из 315) и булочных (157 из 256).
Однако наиболее частые по доле сетевых заведений относительно общего количества - булочные (18,9%), пиццерии (16,1%) и кофейни (15,8%), наименее частые: столовые (8,7%) и бары (6,8%).
Из этого можно сделать вывод, что наиболее часто сетевые модели построения бизнеса используются для кофеен, наименее часто - для столовых.
Топ-15 сетевых заведений по количеству в сети¶
Среди 15 самых популярных сетевых заведений с большим отрывом лидирует сеть кофеен "Шоколадница" (120 заведений, 14,7%). Наименее распространена сеть столовых "Му-Му" (27 заведений, 3,3%)
Из топ-15 наибольшее количество составляют кофейни - 41,2%. Меньше всего столовые - 0,2%.
Топ-15 сетевых заведений сильно выделяются соотношением по категориям: среди них больше доли кофеен (доля больше на 145%) и пиццерий (доля больше на 146,6%), меньше доли заведений быстрого питания (доля меньше на 79,6%) и кафе (доля меньше на 56,8%), почти не представлены бары и столовые.
Подавляющее большинство сетевых заведений из топ-15 не работают круглосуточно вне зависимости от их категории - доля круглосуточных сетевых заведений на 40,9 % меньше, чем всех.
Также доля сетевых заведений из топ-15 несколько меньше в Северо-Западном и Юго-восточном административном округах.
Средний счёт сетевых заведений из топ-15 сосредоточен около значения 471,6, в то время как средний счёт всех заведений распределён более равномерно по большему диапазону значений со средним в 941,5.
Наибольшее количество заведений - в Центральном административном округе.¶
Распределение средних рейтингов заведений по категориям¶
Средние рейтинги заведений по категориям различаются слабо: от 4,05 (быстрое питание) до 4,39 (бар,паб).
Выше всего средние рейтинги в Центральном (4,38), Северном (4,24) и Северо-Западном (4,21) административных округах. Наименьший средний рейтинг в Юго-Восточном административном округе (4,1).
Распределение количества заведений по улицам¶
Больше всего заведений на проспекте Мира (183), меньше всего - на Пятницкой улице (48) и улице Миклухо-Маклая (49).
Улицы, на которых находится только одно заведение¶
Среди заведений, расположенных на улицах только с одним заведением больше всего кафе (160, 34,9%), ресторанов (94, 20,5%) и кофеен (84, 18,3%), однако их доля от общего количества значимо не отличается от доли всех заведений; меньше всего булочных (8, 1,7%), причём доля булочных на 42,8% меньше, чем в общей выборке.
Доли пиццерий и предприятий быстрого питания также существенно меньше: на 56,6% и 30,2% соответственно. Доля столовых, напритив, вдвое больше - на 109,2%.
Доли рассматриваемых заведений ниже при распределении п административным округам:
- в Западном административный округе на 24,6%;
- в Юго-Западном административном округе на 53.5%.
Доля сетевых заведений меньше на 24,3%.
Есть отличие в среднем числе посадочных мест: 59,1 против 108,4 в основной выборке.
Распределение медианного среднего счёта по округам¶
Наибольший медианный средний счёт в Центральном и Западном административном округах - 1000 руб., наименьший - Северо-Восточном (500 руб.), Южном (500 руб.) и Юго-Восточном (450 руб).
Исследование данных для определения рекомендаций¶
Больше всего кофеен в Центральном округе (428, 30,3%), меньше всего в Северо-Западном (62, 4,4%).
Кофеен, работающих в режиме "24/7" почти нет: всего 59 (4,2%).
Сетевых и несетевых кофеен примерно поровну: 726 (51,4%) и 687 (48,6%) соответственно.
Средний рейтинг 4,28, медианный 4,3. Рейтинги распределены близко к среднему, но есть небольшое количество кофеен с низким рейтингом (3,6 и менее). Самый высокий средний рейтинг у кофеен в Центральном административном округе (4,34), самый низкий - в Западном (4,2).
Средняя цена чашки капучино 169,5 руб. Наибольшая средняя цена чашки капучино в Западном (189,9 руб.), Центральном (187,5 руб.) и Юго-Западном административных округах; наименьшая - в Восточном административном округе (140).
Рекомендации для открытия нового заведения¶
Рекомендую основателям фонда «Shut Up and Take My Money» открыть новую кофейню в Центральном административном округе: здесь кофейни пользуются гораздо большим спросом и популярностью, чем в других округах. Из-за большого количества кофеен следует ожидать высокой конкуренции. При определении ценовой политики новой кофейни можно ориентироваться на среднюю цену чашки капучино в 187,5 руб. Количество посадочных мест в кофейне должно быть около 86.